Skip to content

Add store benchmarks - #70

Open
zienag wants to merge 1 commit into
yandex:mainfrom
zienag:store-benchmarks
Open

zienag wants to merge 1 commit into
yandex:mainfrom
zienag:store-benchmarks

Conversation

@zienag

@zienag zienag commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

There are no measurements of what the implicit store costs, so changes to it, such as replacing EntryConcrete<T>, get argued without numbers. This adds a benchmark suite for the store, a README that explains where its instructions and memory go, and a script for comparing builds.

Changes

  • Benchmarks/ is a separate package on ordo-one/benchmark, so the library gains no dependency. It measures retrieving a value, declaring one in a nested scope, and the one-time cost of a type the process has never stored, over 10,000 types that a macro generates.
  • Benchmarks/README.md has the results and explains them:
    • where retrieve and declare spend their cycles;
    • what a new type leaves in memory: 1,043 bytes, 408 of them the EntryConcrete<T> class metadata;
    • measurements of other ways to store the entry.
  • Benchmarks/compare.py compares builds over several interleaved process runs. One run against one run can't call a regression: the store's dictionary is keyed by metadata addresses, so its layout changes with the binary and moves instruction counts by up to 20%.
  • AGENTS.md says how to run the benchmarks.

Test plan

  • The benchmarks package builds in release, and swift package --package-path Benchmarks benchmark runs. CI doesn't build it.

Copilot AI balanced review requested due to automatic review settings October 21, 2026 11:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

5 open issues
What changed in this PR

Adds a standalone benchmark suite (plus comparison tooling/docs) for Implicits’ store performance/memory behavior, and fixes a thread-store lifetime leak by releasing the TSD value when clearing it.

Changes:

  • Introduces Benchmarks/ as a separate SwiftPM package with benchmarks, a macro generator for 10k types, and a Python script to compare builds across interleaved runs.
  • Documents benchmark results, methodology, and execution instructions in Benchmarks/README.md and AGENTS.md.
  • Fixes a RawStore leak by releasing the retained store when clearing thread-specific data; adds a regression test on Darwin.
File Description
Tests/​ImplicitsTests/​ImplicitTests.swift Adds a Darwin-only regression test to detect leaked thread stores via malloc statistics.
Sources/​Implicits/​RawStore.swift Fixes the thread-store leak by releasing the TSD value when clearing it.
Benchmarks/​compare.py Adds a script to compare benchmark builds across multiple interleaved rounds using bootstrap + Mann–Whitney.
Benchmarks/​README.md Documents benchmark scenarios, results, analysis, and storage alternatives.
Benchmarks/​Package.swift Adds a standalone SwiftPM package for benchmarks and the macro target.
Benchmarks/​Package.resolved Pins benchmark dependencies for the standalone package.
Benchmarks/​Macros/​ImplicitsBenchmarksMacros/​NewTypesMacro.swift Adds a macro to generate 10,000 specialized types and touch closures.
Benchmarks/​ImplicitsBenchmarks/​ImplicitsBenchmarks.swift Adds benchmark definitions, runner setup, and helper harnesses.
AGENTS.md Documents how to run benchmarks and compare results.

Review effort: Lite

Comment on lines 201 to 207
private static func clearTSD() {
guard let ptr = pthread_getspecific(key) else {
return
}
pthread_setspecific(key, nil)
Unmanaged<RawStore>.fromOpaque(ptr).release()
}
Comment thread Benchmarks/compare.py
Comment on lines +43 to +52
def mann_whitney_p(a, b):
"""Exact two-sided p-value: the share of all splits of the pooled runs whose
rank-sum statistic is at least as far from its mean as the observed one."""
pooled = sorted(a + b)
rank = {v: statistics.mean(i + 1 for i, w in enumerate(pooled) if w == v) for v in pooled}
ranks = [rank[v] for v in a + b]
mean = len(a) * (len(pooled) + 1) / 2
observed = abs(sum(ranks[: len(a)]) - mean)
splits = [abs(sum(c) - mean) >= observed - 1e-9 for c in itertools.combinations(ranks, len(a))]
return sum(splits) / len(splits)
Comment on lines +267 to +268
// Byte counts are process-wide and tests run in parallel, so the smallest growth
// of several attempts is the one least disturbed by other tests.
Comment on lines +269 to +280
nonisolated(unsafe) var growth = Int.max
let finished = DispatchSemaphore(value: 0)
let thread = Thread {
for _ in 0..<5 {
let before = bytesInUse()
for _ in 0..<10000 {
rootScope()
}
growth = min(growth, bytesInUse() - before)
}
finished.signal()
}
Comment on lines +8 to +14
precondition(
ProcessInfo.processInfo.environment["SWIFT_DETERMINISTIC_HASHING"] == "1",
"""
Run with SWIFT_DETERMINISTIC_HASHING=1: the store is a dictionary, \
and a per-process hash seed changes instruction counts between runs by up to 30%.
"""
)
@zienag zienag changed the title Add store benchmarks and release the thread store when its root scope ends Add store benchmarks Oct 26, 2026
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