# Native measurement notes

## 01 / Scope and artifact identity

This is a measurement/presentation addition, not a scheduler optimization. The five runtime project names, GUIDs and output types remain unchanged. `ThreadPool/PerformanceTests1` is a standalone **C17 console executable**: its only compiled source is `ThreadPoolNativeBenchmarks.c`, built with `/TC /std:c17`. It has no C++ adapter, classes, STL, exceptions or C++ test-framework dependency. A compile-time guard rejects accidental C++ compilation.

The harness exercises the **real** production DLLs, whose implementation remains C++. `PriorityQueueDLL.h` now guards its existing `extern "C"` declarations with `__cplusplus`, making it valid in both languages without changing exports or layout. The pool already exposes C-compatible types but MSVC C++-decorated function names. A C-only setup helper resolves each unique public name from the loaded DLL's export table and obtains it with `GetProcAddress`; this preserves the production ABI. The helper rejects ambiguous overloads. This binding targets the existing MSVC/Win32 ABI, not a new portable C library API. DLL loading and symbol resolution are outside every measured interval. Pool/queue algorithms and shutdown behavior are unchanged.

The Sleep-based 200-job ClientApp demonstration remains a regression/stress demonstration. It is **not** used as a throughput benchmark.

- Measurement source: [`ThreadPoolNativeBenchmarks.c`](../ThreadPool/PerformanceTests1/ThreadPoolNativeBenchmarks.c).
- Reproduction/publishing: [`Build/Benchmark.ps1`](../Build/Benchmark.ps1).
- Raw observations: [`assets/native-runs.csv`](../assets/native-runs.csv).
- Environment, source SHA-256 manifest, methodology, summaries and observations: [`assets/benchmarks.json`](../assets/benchmarks.json).
- Offline browser copy: `assets/benchmark-data.js`, generated from the same JSON; no invented fallback data.

The C-only harness is **artifact version 2**, with newly collected timings. The former C++ source, recipe and observations are preserved as inactive historical text/data under [`history/cpp-harness/`](history/cpp-harness/README.md). They are not compiled or presented as C measurements. The successful C measurement source is frozen for this capture; a language/host change is not a production optimization or a valid before/after speedup comparison.

## 02 / Reproduce

Prerequisites: Windows, Visual Studio 2026 Desktop development with C++ (which includes the C compiler), C17 support, v145, and Windows SDK 10.0.26100.0. No native C++ test tools, benchmark package or browser package is required.

From the repository root:

```powershell
powershell -File Build/Benchmark.ps1 -Platform x64 -Publish
```

The script uses `vswhere`, rebuilds the C executable and its project-reference dependencies, then runs `PerformanceTests1.exe` directly. It does not attach a profiler/debugger or use VSTest/MSTest. It requires exit code zero, the native success marker, C17/artifact-v2 metadata, the expected instrumentation label and all 182 measured CSV rows before publishing. Missing/failed tests never substitute example timings. An earlier published capture remains intact after a failed new run; always inspect its timestamp.

Omit `-Publish` to leave the current website capture untouched. Use `-Platform Win32` for a separate 32-bit run; publishing replaces the displayed capture and metadata, not merges incompatible runs. The displayed capture is **x64 Release C17**; platform runs are never pooled together.

Outputs are isolated from ordinary diagnostic builds:

- `artifacts/benchmark/bin/<Platform>/Release/`: C executable, production DLLs, native CSV/metadata.
- `artifacts/benchmark/obj/`: measurement-build objects.
- `artifacts/benchmark/results/<Platform>/`: private build/stdout/stderr logs and assembled JSON.
- `assets/`: allowlisted public JSON, matching JavaScript wrapper, CSV, when publishing.

For the ordinary regression matrix, independently run:

```powershell
powershell -File Build/Test.ps1 -Stress
```

Do not run that matrix concurrently with measurements. Close other CPU-intensive applications, use a stable power plan, and repeat full sessions to assess reproducibility. This initial capture did not pin affinity or isolate the machine from all background activity.

## 03 / Instrumentation control

`NativeBenchmark=true` is a global MSBuild property. The shared settings apply `Build/BenchmarkInstrumentation.h` as a forced include in **every module**, including the static libraries and both DLLs. It disables `LOG_TRACE`, `LOG_INFO`, and function-entry/exit trace macros, including argument evaluation. Default builds do not use that header or those isolated paths.

This deliberately addresses the separate logger instance in each DLL. Redirecting stdout or changing ClientApp's logger alone would leave formatting, logger locks and `OutputDebugString` work on hot paths. The forced include avoids all trace/info call sites consistently, without pretending that quiet stdout removes their cost.

Retained: warnings/errors/critical logging, assertions, work allocation/free, all pool/queue/list critical sections, semaphore operations, diagnostic slots, worker clocks/counters, per-task harness atomics and completion signaling. Native initialization/shutdown/queue lifecycle still contain blank `printf` calls. They are outside throughput intervals but inside relevant lifecycle intervals.

These are **quiet-instrumentation Release-build measurements**, not ordinary diagnostic-build performance. No paired diagnostic timing was collected, so no numeric logging overhead or logging-removal speedup is claimed. Diagnostic behavior and its potential cost remain documented rather than hidden.

## 04 / Workload, warmup and timed boundaries

Each of 26 scenarios has **two discarded, validated warmups** followed by **seven measured repetitions**. Workers rotate in scenario order between repetitions to reduce, not remove, thermal/order bias. Fresh pools are created per repetition. All fixed workers reach the idle-count ready state before throughput timing; producer threads are already created and gated.

QPC frequency is read with `QueryPerformanceFrequency`. Raw QPC ticks are retained; ms = ticks × 1000 / frequency. Summaries report minimum, median and maximum, not percentiles or confidence intervals from seven samples.

| Scenario | Workload | Timed boundary | Excluded |
| --- | --- | --- | --- |
| Throughput, 1/4/8 fixed workers | 12,000 tasks, 1 producer, 0 or 2,048 dependent 64-bit xorshift/multiply iterations per callback | QPC just before producer-release event through all producer joins and final completion-event observation | Input creation, pool/producer creation, waiting for readiness, shutdown, queue cleanup, final per-task checksum recomputation |
| Many-producer stress | 48,000 tasks, 16 simultaneous producers, 8 fixed workers, 2,048 iterations | Same throughput boundary | Same exclusions |
| Serial create-work-join-close | 256 sequential native threads, 0 or 2,048 iterations each | Before first `CreateThread` through final thread wait/`CloseHandle` | Task input allocation and post-run validation |
| Pool initialize-shutdown | 1/4/8 fixed workers, no jobs | Before `InitializeThreadPool` through `ShutdownThreadPool` return | Queue initialization and cleanup |

The integer function is non-inlined, uses unsigned wraparound arithmetic, starts from each task ID, publishes a consumed result, and is recomputed for every task after shutdown. No `Sleep` occurs in timed callbacks. The zero-iteration workload is a **minimal callback plus validation**, not a truly empty callback or a pure scheduler-overhead measurement.

The serial thread baseline includes native creation, OS dispatch, entry/exit, the workload, validation counters, wait, handle cleanup and main-thread success checks. It is **not bare `CreateThread` API latency**, nor a concurrent thread-per-job throughput test. Its completion bookkeeping differs from the pooled aggregate event path, and its batch size differs; do not use it as a like-for-like pool speedup ratio.

Pool lifecycle includes events/semaphore/critical-section setup and actual worker handle joins. Initialization return does not guarantee every worker has started; immediate shutdown can occur before any callback would have run. It is deliberately labeled a combined lifecycle baseline.

## 05 / Priority distributions and exact validation

Priority values are HIGH=0, MEDIUM=1, LOW=2. Every throughput/stress workload is measured with:

- Uniform: exactly one third at each priority.
- `high90`: exactly HIGH/MEDIUM/LOW = 90%/5%/5%.
- `low90`: exactly HIGH/MEDIUM/LOW = 5%/5%/90%.

Inputs are Fisher–Yates shuffled using the explicit LCG seed `0x5eed1234`. Counts are recorded per run. Producers submit disjoint strided task IDs from a preallocated array. Producer readiness is gated before the timed release. Every call result and every task has independent counters; aggregate completion alone is insufficient to pass.

After producer joins and `ShutdownThreadPool` returns, **every task ID** must have:

- accepted = executed = completed = succeeded = 1;
- cancelled = unexpected/stuck outcomes = 0;
- its expected checksum, not just a global sum;
- an empty priority queue after join.

Only validated runs emit result rows. CSV count columns report the totals established by those per-task assertions; they are not estimates from throughput. Duplicate notifications, missing jobs, rejected submissions, unexpected cancellations or incorrect results fail the suite. A separate, untimed preloaded 90-node queue check verifies the exact pointer order across priorities and FIFO ties. Parallel callback start/finish order is not tested as if it were dequeue order.

Contexts, queue state and callback code remain alive through safe shutdown. Explicit C cleanup paths handle partial allocation, event and thread-creation failures: join producers, cancel queued jobs, join all running workers, then clean up the queue and contexts. Task arrays use 64-byte-aligned allocation, matching the previous harness's alignment. No thread is forcibly terminated. Completion/readiness timeouts fail the test only after safe cleanup; a genuine deadlock can prevent cleanup from returning. The PowerShell runner has a separate 15-minute **whole-process watchdog** (configurable with `-TimeoutSeconds`); killing a failed test process is not a bounded-shutdown feature or a passing result.

The finite stress runs demonstrate no lost/duplicate tasks **in those runs**, not mathematical proof across all schedules or fault injection.

## 06 / Observed C17 artifact-v2 results

New C17 capture on **2026-09-12 UTC**; the exact timestamp is recorded in the JSON. Intel Core i7-9700K, 8 cores / 8 logical processors, Windows 11 Enterprise build 26200, 63.8 GiB visible memory, Ultimate Performance power plan. x64 Release, v145, compiler `_MSC_FULL_VER=195136257`, C standard `201710`, SDK 10.0.26100.0, QPC 10 MHz. Default OS affinity and priority; not a controlled laboratory machine. The JSON stores allowlisted metadata, not usernames, hostnames or absolute source paths.

**Uniform priorities, 12,000-task single-producer batches:**

| Worker count | Callback iterations | Median tasks/s | Min / median / max batch ms |
| --- | --- | --- | --- |
| 1 | 0 | 866,532 | 13.471 / 13.848 / 15.093 |
| 4 | 0 | 601,043 | 18.780 / 19.965 / 23.834 |
| 8 | 0 | 102,095 | 108.347 / 117.538 / 120.461 |
| 1 | 2,048 | 186,283 | 63.818 / 64.418 / 65.278 |
| 4 | 2,048 | 538,322 | 21.070 / 22.292 / 26.588 |
| 8 | 2,048 | 212,053 | 23.642 / 56.590 / 103.707 |

**Other observed boundaries:**

| Scenario | Median | Observed batch range |
| --- | --- | --- |
| 16 producers / 8 workers, uniform, 48,000 tasks | 577,160 tasks/s; 83.166 ms | 81.493–90.095 ms |
| Serial create-work-join-close, minimal callback, 256 threads | 39.807 ms/batch; 0.1555 ms/thread cycle | 38.861–44.968 ms/batch |
| Serial create-work-join-close, 2,048 iterations, 256 threads | 39.345 ms/batch; 0.1537 ms/thread cycle | 37.862–45.334 ms/batch |
| Pool initialize-shutdown, 1 worker | 0.2808 ms/cycle | 0.1659–0.3139 ms |
| Pool initialize-shutdown, 4 workers | 0.6235 ms/cycle | 0.4046–0.6788 ms |
| Pool initialize-shutdown, 8 workers | 0.9828 ms/cycle | 0.6673–1.2205 ms |

All **182 measured repetitions** passed, covering **2,523,584 measured tasks**, including **1,008,000 many-producer tasks**. Warmup tasks were also validated but excluded from those totals. All three priority mixes and all individual observations are in the downloads and website tables.

The same C17 artifact also built and passed all 182 measured repetitions on **Win32 Release**, independently and without publishing over the x64 capture. Its local observations are in `artifacts/benchmark/results/Win32/benchmarks.json`. These platform-specific timings are not merged or presented as a language speedup.

### Interpretation, without inventing a cause

- Four workers gave the highest median throughput for this single-producer integer workload; eight did not improve on four and had a much larger spread. All samples are preserved, not discarded as inconvenient outliers.
- For the minimal callback, adding workers reduced observed throughput. The source has serialized pool/queue/list paths and shared validation counters, but QPC alone cannot attribute cost to a particular lock, logger, context switch or cache line.
- More producers changed the operating conditions substantially. The many-producer case has a different task count and submission pattern; it is not directly interchangeable with the 12,000-task single-producer case.
- The slightly lower serial baseline with useful work is within session variation; it is not evidence that extra work accelerates thread creation.
- A CPU/contention trace and additional independent sessions would be appropriate before any production optimization. No optimization or before/after speedup is claimed here.

## 07 / Limitations and publication hygiene

No pinned affinity, background-process isolation, long-duration stability study, latency percentiles, starvation bound, I/O workload, dynamic growth/reaping benchmark, cancellation-latency measurement, or OS-resource fault injection. Default process/thread priorities are used rather than explicitly set/sampled. Checksums and per-task counters are intentional harness overhead. The caller-side completion event can be observed before the final completion function epilogue returns; shutdown joins everything before validation, outside the throughput interval.

The public dataset includes relative-path SHA-256 source fingerprints for the C measurement source and C++ production sources, including preserved sources; not every fingerprinted file is linked into the executable. The project build inputs and active implementation are identified above. Keep raw logs and PDBs under ignored `artifacts/`: those may contain machine paths. Do not copy private native logs into the site.

The static numbers in this section describe the first **C17 artifact-v2** capture, not the archived C++ capture. Republishing updates the website's live tables and metadata automatically; update or retain this section explicitly rather than silently treating it as a new capture. Changing C++/MSTest to a C executable changes the host and harness overhead; do not attribute cross-artifact timing differences solely to the source language.
