From e4f268c34c093c1cfcac0e5a66b25d398daf3e9e Mon Sep 17 00:00:00 2001 From: Rex Morgan Date: Wed, 5 Aug 2026 00:28:36 -0400 Subject: [PATCH 1/2] bench: enable MemoryDiagnoser on LargeArray and EndToEnd Co-Authored-By: Claude Fable 5 --- source/Handlebars.Benchmark/EndToEnd.cs | 1 + source/Handlebars.Benchmark/LargeArray.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/source/Handlebars.Benchmark/EndToEnd.cs b/source/Handlebars.Benchmark/EndToEnd.cs index 379ab152..6bbf78ad 100644 --- a/source/Handlebars.Benchmark/EndToEnd.cs +++ b/source/Handlebars.Benchmark/EndToEnd.cs @@ -8,6 +8,7 @@ namespace HandlebarsNet.Benchmark { + [MemoryDiagnoser] public class EndToEnd { private object? _data; diff --git a/source/Handlebars.Benchmark/LargeArray.cs b/source/Handlebars.Benchmark/LargeArray.cs index 7b367e34..d7555393 100644 --- a/source/Handlebars.Benchmark/LargeArray.cs +++ b/source/Handlebars.Benchmark/LargeArray.cs @@ -7,6 +7,7 @@ namespace HandlebarsNet.Benchmark { + [MemoryDiagnoser] public class LargeArray { private object _data = null!; // -> Setup() From 7c1eaef67cda1f8f265106c406bc9a83d0e44d0d Mon Sep 17 00:00:00 2001 From: Rex Morgan Date: Wed, 5 Aug 2026 00:35:44 -0400 Subject: [PATCH 2/2] perf: enlarge BoxedValues int cache from 20 to 1024 Iterators box the per-item index through BoxedValues.Int on every iteration; indexes beyond the cache allocate a fresh 24-byte box each time, which is the dominant remaining allocation in list rendering (e.g. 23.5KB per render of a 1000-item {{#each}}). 1024 cached boxes (~32KB of process-lifetime statics) cover effectively all template loops. Co-Authored-By: Claude Fable 5 --- source/Handlebars/Runtime/BoxedValues.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Handlebars/Runtime/BoxedValues.cs b/source/Handlebars/Runtime/BoxedValues.cs index d2bfc5fa..5918f399 100644 --- a/source/Handlebars/Runtime/BoxedValues.cs +++ b/source/Handlebars/Runtime/BoxedValues.cs @@ -11,7 +11,10 @@ namespace HandlebarsDotNet.Runtime /// public static class BoxedValues { - private const int BoxedIntegersCount = 20; + // Covers iterator indexes for effectively all template loops. The cache is + // ~32KB of process-lifetime statics (1024 boxes + the array), traded against + // a 24-byte allocation per iteration index >= the cache size on every render. + private const int BoxedIntegersCount = 1024; private static readonly object[] BoxedIntegers = new object[BoxedIntegersCount]; static BoxedValues()