Skip to content

Serde ops/CPU-sec harness: log-space geomean, pipeline breakdown, --threads, profiling recipe - #4557

Open
jnunn-aws wants to merge 1 commit into
developmentfrom
jonatnun/ocs-harness
Open

jnunn-aws wants to merge 1 commit into
developmentfrom
jonatnun/ocs-harness

Conversation

@jnunn-aws

@jnunn-aws jnunn-aws commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Test-only changes to the serde ops/CPU-sec runner. The SDK isn't touched.

  • Geomean overflow fix: the overall geometric mean multiplied all 71 per-case results before taking the root, which overflows double once the geomean passes about 21,900 ops/CPU-sec and prints infinity. Both the overall and the per-protocol geomeans are now computed in log space, as ocs-sop.md (Geometric Mean Calculation) specifies. Below the overflow the value is unchanged.
  • --case-filter REGEX: runs only the matching cases.
  • --breakdown / --breakdown-csv: per-stage ns/op and B/op for each pipeline handler in both directions, with the stub transport timed separately. Requires --threads 1 and --transport stub.
  • --threads N: N concurrent workers per case on a shared client, as a regression check for state shared between calls. Not the cross-SDK metric; the default (1) runs the existing loop unchanged.
  • profiling/profile.sh + perf_report.py: a steady-state perf sampling profile of one case on Linux, keeping only samples after tiered compilation finishes. Diagnostic only.
  • README documents the options next to the --transport options from Add a selectable HTTPS loopback transport to the ops/CPU-sec mode #4552. The profiling build recipe is tested on awsJson cases.

With no new options, the measured loop is unchanged; the only addition on the default path is a disabled-probe check (one static bool) at entry and exit of MockHttpHandler.SendAsync.

Motivation and Context

The overflow made summaries unusable as results improved. The breakdown and profile show where per-call CPU goes, which is how the other PRs in this series were found and checked.

Testing

Built the runner; ran stub mode, --breakdown (+CSV), --threads 2, and checked that each invalid combination exits 2 (--breakdown --transport https, --breakdown --threads 2, --threads without --cpu-time, --case-filter/--breakdown/--breakdown-csv without --cpu-time, --breakdown-csv without --breakdown, missing or invalid values for --case-filter/--threads/--breakdown-csv, and a --case-filter that matches no case). The warmup default (100,000) is unchanged.

Breaking Changes Assessment

None. Test code only.

Dry-runs

Test-only change under sdk/test/Performance.

  • DotNet Dry-run ID: e2c4cb0e-523d-48d5-88d5-183ac33bd05f
    • Completed successfully
  • PowerShell Dry-run ID: fa1208b4-23b1-44de-b776-9014c72515e8
    • Completed successfully

Checklist

  • My code follows the code style of this project
  • I have added tests to cover my changes (no unit tests; manual checks listed under Testing)
  • All new and existing tests passed

License

  • I confirm that this pull request can be released under the Apache 2 license

@dscpinheiro

Copy link
Copy Markdown
Contributor

Added @boblodgett as a reviewer since I'm curious whether the --threads N option makes sense (I assume we'd need every SDK to implement this which may not be simple).

…hreads, profiling recipe

- Geometric mean computed in log space. The direct product of the 71
  per-case results overflows double once the geomean exceeds about 21,900
  ops/CPU-sec, which turned the overall and per-protocol summaries into
  infinity.
- --case-filter REGEX runs only the matching cases. Skipped cases return
  null from MeasureAsync; CpuTimeBenchmarks.cs and
  generate_serde_benchmarks.py carry the matching nullable result type.
- --breakdown links a probe handler above the pipeline and below every
  handler through RuntimePipelineCustomizerRegistry, and reports per-stage
  ns/op and B/op in both directions, with the stub transport timed
  separately (MockHttpHandler stamps its own entry and exit).
  --breakdown-csv appends the rows to a CSV file. It requires --threads 1
  and --transport stub: the probes record into static slots, and only the
  in-process stub stamps the transport stage.
- --threads N runs N concurrent workers per case on the shared client and
  request, as a regression check for SDK state shared between calls. It
  is not the cross-SDK metric. Warmup is split across the workers, and the
  run stops at --min-iterations x N total ops or --min-cpu-seconds x N
  process CPU. The 12 cases whose shared request holds a MemoryStream are
  skipped when N > 1. --threads 1 (the default) runs the existing loop
  unchanged.
- profiling/profile.sh and perf_report.py: a steady-state perf sampling
  profile of one case on Linux, keeping only the samples taken after
  tiered compilation finishes, with per-stage and per-thread summaries
  and collapsed stacks. Diagnostic only.
- README documents the new options next to the --transport options from
  #4552.
@jnunn-aws
jnunn-aws force-pushed the jonatnun/ocs-harness branch from 8d1b7ef to 33780d0 Compare October 28, 2026 23:25
@jnunn-aws

Copy link
Copy Markdown
Contributor Author

Added @boblodgett as a reviewer since I'm curious whether the --threads N option makes sense (I assume we'd need every SDK to implement this which may not be simple).

@dscpinheiro other SDKs don't need to implement it. It's a .NET-only regression check for state shared between concurrent calls on one client (locks, caches, pools), and it's never part of the cross-SDK score; without it the runner behaves exactly as before. It's how we checked that the perf PRs don't add contention: at 16 threads, feature/4.1 kept 11.3 cores busy on x86 against 13.0 with all the changes. If Bo would rather keep it out of the shared harness, I can drop it from this PR.

@jnunn-aws

Copy link
Copy Markdown
Contributor Author

@boblodgett scored-path cost of the MarkMockEnter/MarkMockExit checks, with --breakdown off:

Run 1 Run 2 Run 3 Median
Base (f9e9769) 14,341 14,460 14,591 14,460
This PR (33780d0) 14,359 14,377 14,417 14,377

−0.6%, inside the base runs' 1.7% spread, so no measurable cost (the change is two static-bool checks per call). Geomean of the 71 cases, stub, net10.0, 100k warmup, 3 interleaved rounds per side, dev host (not metal).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants