Skip to content

Commit edf09c9

Browse files
committed
Optimize memory layout by boxing large structs. Slightly reduces memory usage.
1 parent 540f3d3 commit edf09c9

File tree

2 files changed

+31
-19
lines changed

2 files changed

+31
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## v0.35
44
- Fix tooltips not showing on line charts with one or more hidden series
55
- Update default chart colors and text shadows for better readability with all themes
6+
- Optimize memory layout by boxing large structs. Slightly reduces memory usage.
67

78
## v0.34 (2025-03-23)
89

src/render.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use actix_web::http::{header, StatusCode};
5252
use actix_web::{HttpResponse, HttpResponseBuilder, ResponseError};
5353
use anyhow::{bail, format_err, Context as AnyhowContext};
5454
use awc::cookie::time::Duration;
55-
use handlebars::{BlockContext, Context, JsonValue, RenderError, Renderable};
55+
use handlebars::{BlockContext, JsonValue, RenderError, Renderable};
5656
use serde::Serialize;
5757
use serde_json::{json, Value};
5858
use std::borrow::Cow;
@@ -510,7 +510,8 @@ impl<W: std::io::Write> JsonBodyRenderer<W> {
510510
}
511511

512512
pub struct CsvBodyRenderer {
513-
writer: csv_async::AsyncWriter<AsyncResponseWriter>,
513+
// The writer is a large struct, so we store it on the heap
514+
writer: Box<csv_async::AsyncWriter<AsyncResponseWriter>>,
514515
columns: Vec<String>,
515516
}
516517

@@ -550,7 +551,7 @@ impl CsvBodyRenderer {
550551
tokio::io::AsyncWriteExt::flush(&mut async_writer).await?;
551552
let writer = builder.create_writer(async_writer);
552553
Ok(CsvBodyRenderer {
553-
writer,
554+
writer: Box::new(writer),
554555
columns: vec![],
555556
})
556557
}
@@ -870,14 +871,20 @@ impl<W: std::io::Write> handlebars::Output for HandlebarWriterOutput<W> {
870871

871872
pub struct SplitTemplateRenderer {
872873
split_template: Arc<SplitTemplate>,
873-
local_vars: Option<handlebars::LocalVars>,
874-
ctx: Context,
874+
// LocalVars is a large struct, so we store it on the heap
875+
local_vars: Option<Box<handlebars::LocalVars>>,
876+
ctx: Box<handlebars::Context>,
875877
app_state: Arc<AppState>,
876878
row_index: usize,
877879
component_index: usize,
878-
nonce: JsonValue,
880+
nonce: u64,
879881
}
880882

883+
const _: () = assert!(
884+
std::mem::size_of::<SplitTemplateRenderer>() <= 64,
885+
"SplitTemplateRenderer should be small enough to be allocated on the stack"
886+
);
887+
881888
impl SplitTemplateRenderer {
882889
fn new(
883890
split_template: Arc<SplitTemplate>,
@@ -890,9 +897,9 @@ impl SplitTemplateRenderer {
890897
local_vars: None,
891898
app_state,
892899
row_index: 0,
893-
ctx: Context::null(),
900+
ctx: Box::new(handlebars::Context::null()),
894901
component_index,
895-
nonce: nonce.into(),
902+
nonce,
896903
}
897904
}
898905
fn name(&self) -> &str {
@@ -920,7 +927,7 @@ impl SplitTemplateRenderer {
920927
.block_mut()
921928
.expect("context created without block");
922929
blk.set_local_var("component_index", self.component_index.into());
923-
blk.set_local_var("csp_nonce", self.nonce.clone());
930+
blk.set_local_var("csp_nonce", self.nonce.into());
924931

925932
*self.ctx.data_mut() = data;
926933
let mut output = HandlebarWriterOutput(writer);
@@ -930,9 +937,11 @@ impl SplitTemplateRenderer {
930937
&mut render_context,
931938
&mut output,
932939
)?;
933-
self.local_vars = render_context
934-
.block_mut()
935-
.map(|blk| std::mem::take(blk.local_variables_mut()));
940+
let blk = render_context.block_mut();
941+
if let Some(blk) = blk {
942+
let local_vars = std::mem::take(blk.local_variables_mut());
943+
self.local_vars = Some(Box::new(local_vars));
944+
}
936945
self.row_index = 0;
937946
Ok(())
938947
}
@@ -948,12 +957,12 @@ impl SplitTemplateRenderer {
948957
let blk = render_context
949958
.block_mut()
950959
.expect("context created without block");
951-
*blk.local_variables_mut() = local_vars;
960+
*blk.local_variables_mut() = *local_vars;
952961
let mut blk = BlockContext::new();
953962
blk.set_base_value(data);
954963
blk.set_local_var("component_index", self.component_index.into());
955964
blk.set_local_var("row_index", self.row_index.into());
956-
blk.set_local_var("csp_nonce", self.nonce.clone());
965+
blk.set_local_var("csp_nonce", self.nonce.into());
957966
render_context.push_block(blk);
958967
let mut output = HandlebarWriterOutput(writer);
959968
self.split_template.list_content.render(
@@ -963,9 +972,11 @@ impl SplitTemplateRenderer {
963972
&mut output,
964973
)?;
965974
render_context.pop_block();
966-
self.local_vars = render_context
967-
.block_mut()
968-
.map(|blk| std::mem::take(blk.local_variables_mut()));
975+
let blk = render_context.block_mut();
976+
if let Some(blk) = blk {
977+
let local_vars = std::mem::take(blk.local_variables_mut());
978+
self.local_vars = Some(Box::new(local_vars));
979+
}
969980
self.row_index += 1;
970981
}
971982
Ok(())
@@ -983,12 +994,12 @@ impl SplitTemplateRenderer {
983994
let mut render_context = handlebars::RenderContext::new(None);
984995
local_vars.put("row_index", self.row_index.into());
985996
local_vars.put("component_index", self.component_index.into());
986-
local_vars.put("csp_nonce", self.nonce.clone());
997+
local_vars.put("csp_nonce", self.nonce.into());
987998
log::trace!("Rendering the after_list template with the following local variables: {local_vars:?}");
988999
*render_context
9891000
.block_mut()
9901001
.expect("ctx created without block")
991-
.local_variables_mut() = local_vars;
1002+
.local_variables_mut() = *local_vars;
9921003
let mut output = HandlebarWriterOutput(writer);
9931004
self.split_template.after_list.render(
9941005
&self.app_state.all_templates.handlebars,

0 commit comments

Comments
 (0)