Skip to content

Adapt dotnet memory allocations#705

Open
ricardoboss wants to merge 17 commits intobencherdev:develfrom
ricardoboss:issues/699-dotnet-memory-allocations
Open

Adapt dotnet memory allocations#705
ricardoboss wants to merge 17 commits intobencherdev:develfrom
ricardoboss:issues/699-dotnet-memory-allocations

Conversation

@ricardoboss
Copy link

Fixes #699

Adds handling for the memory key in BenchmarkDotNet, in case C# benchmarks are run with the [MemoryDiagnoser] attribute.
If found, adds a new metric allocated measured in bytes.

@ricardoboss ricardoboss changed the base branch from main to devel March 14, 2026 20:28
@ricardoboss ricardoboss force-pushed the issues/699-dotnet-memory-allocations branch from 2841ffc to 8edf525 Compare March 14, 2026 20:31
Copy link
Member

@epompeii epompeii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ricardoboss thank you for all of your work so far!
I've made a few suggestions and had a couple of questions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to get a smaller example file or are they all this large?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah of course, it was just easily accessible for me. I can provide a smaller one

pub mod dotnet {
use bencher_valid::{BYTES, NANOSECONDS};

create_measure!(Latency, "Latency", "latency", NANOSECONDS);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to create a new Latency Measure, we should just use the existing one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ok, I thought every adapter should define its own set. Will switch to the default measure then

Some(results_map.into())
}

pub fn new_dotnet(benchmark_metrics: Vec<(BenchmarkName, Vec<DotNetMeasure>)>) -> Option<Self> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, lets move this method above new_iai. Adapters are (mostly) sorted alphabetically across the code base.

}

#[test]
fn adapter_c_sharp_dot_net_memory() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the harness collect both memory and wall-clock time measurements at the same time or are they mutually exclusive?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is above my paygrade... I have no idea 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it does:

Yes, BenchmarkDotNet collects both simultaneously (well, technically in separate runs to avoid interference, but in the same invocation). When you add [MemoryDiagnoser], the harness:

  1. Runs timing measurements normally
  2. Performs a separate run for memory/GC metrics (so they don't skew latency numbers)
  3. Combines everything into one unified JSON output per benchmark

In the sample file, there are both:

{                                                                                                                                                                                                                                                                                        
    "Method": "Tokenize",                                                                                                                                                                                                                                                                  
    "Parameters": "ExampleFileName=expressions.step",                                                                                                                                                                                                                                      
    "Statistics": {                                                                                                                                                                                                                                                                        
      "Mean": 106.338...,                                                                                                                                                                                                                                                                  
      "Median": 105.607...,
      "StandardDeviation": 3.223...,
      ...
    },
    "Memory": {
      "Gen0Collections": 168,
      "Gen1Collections": 0,
      "Gen2Collections": 0,
      "TotalOperations": 4194304,
      "BytesAllocatedPerOperation": 672
    }
  }

With that being the case, we're going to want to make sure we:

  1. Always collect the wall-clock time measurements
  2. Optionally collect the memory measurements (if they are present)

Right now, it seems like we are only collecting the memory measurements if they are present (skipping 1.).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow... How is the statistics object relevant for the memory allocations? Isn't this used for the latency measurement? That part should mostly be untouched by my changes (latency is still being collected)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Then we just need to make sure we are testing that the latency measures are still being collected properly.

That is, this adapter_c_sharp_dot_net_memory test should assert on each expected measure (including Latency).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem

);
}

pub mod dotnet {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, lets move this below json and above iai.

@ricardoboss ricardoboss requested a review from epompeii March 15, 2026 22:26
@ricardoboss ricardoboss marked this pull request as ready for review March 15, 2026 22:27
@ricardoboss ricardoboss force-pushed the issues/699-dotnet-memory-allocations branch from 18c15d6 to 315621f Compare March 16, 2026 23:00
@ricardoboss ricardoboss requested a review from epompeii March 17, 2026 01:09
@github-actions
Copy link

🐰 Bencher Report

Branchissues/699-dotnet-memory-allocations
Testbedubuntu-22.04
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
Adapter::Json📈 view plot
🚷 view threshold
3.90 µs
(+12.31%)Baseline: 3.48 µs
4.62 µs
(84.44%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
3.98 µs
(+15.17%)Baseline: 3.46 µs
4.52 µs
(88.02%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
25.39 µs
(-1.04%)Baseline: 25.65 µs
31.05 µs
(81.75%)
Adapter::Rust📈 view plot
🚷 view threshold
2.91 µs
(+2.06%)Baseline: 2.85 µs
3.34 µs
(87.20%)
Adapter::RustBench📈 view plot
🚷 view threshold
2.89 µs
(+1.60%)Baseline: 2.85 µs
3.32 µs
(87.21%)
head_version_insert/batch/10📈 view plot
🚷 view threshold
109.25 µs
(+9.09%)Baseline: 100.15 µs
120.45 µs
(90.70%)
head_version_insert/batch/100📈 view plot
🚷 view threshold
250.60 µs
(+5.59%)Baseline: 237.33 µs
266.98 µs
(93.86%)
head_version_insert/batch/255📈 view plot
🚷 view threshold
481.82 µs
(+4.47%)Baseline: 461.21 µs
492.34 µs
(97.86%)
head_version_insert/batch/50📈 view plot
🚷 view threshold
170.48 µs
(+6.27%)Baseline: 160.42 µs
182.51 µs
(93.41%)
threshold_query/join/10📈 view plot
🚷 view threshold
162.62 µs
(+12.60%)Baseline: 144.42 µs
170.07 µs
(95.62%)
threshold_query/join/20📈 view plot
🚷 view threshold
174.69 µs
(+10.17%)Baseline: 158.57 µs
186.03 µs
(93.90%)
threshold_query/join/5📈 view plot
🚷 view threshold
150.22 µs
(+10.11%)Baseline: 136.42 µs
159.81 µs
(94.00%)
threshold_query/join/50📈 view plot
🚷 view threshold
218.66 µs
(+9.34%)Baseline: 199.99 µs
232.82 µs
(93.92%)
🐰 View full continuous benchmarking report in Bencher

@epompeii epompeii removed the claude label Mar 17, 2026
Copy link
Member

@epompeii epompeii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ricardoboss it is looking good!
You just need to run some final cleanup and you should be good to go.

  1. cargo fmt
  2. cargo clippy (to be extra sure you can run ./scripts/clippy.sh to run exactly what is run in CI)

@ricardoboss ricardoboss force-pushed the issues/699-dotnet-memory-allocations branch from 50468e1 to 3dd5e41 Compare March 20, 2026 10:17
@ricardoboss ricardoboss requested a review from epompeii March 20, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

c_sharp_dot_net adapter should support memory allocations

2 participants