Skip to content

Commit 0b656b6

Browse files
authored
Merge pull request swiftlang#39340 from lorentey/fix-benchmarks
[benchmark] Fix spurious benchmark regressions
2 parents 38df7b1 + 386ae58 commit 0b656b6

File tree

5 files changed

+69
-9
lines changed

5 files changed

+69
-9
lines changed

benchmark/README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,30 @@ benchmarks will be compiled with -Onone!**
199199
* `$ ./Benchmark_O --tags=Dictionary`
200200
* `$ ./Benchmark_O --skip-tags=unstable,skip,validation`
201201

202-
### Note
202+
### Deterministic Hashing Requirement
203+
204+
To run benchmarks, you'll need to disable randomized hash seeding by setting the
205+
`SWIFT_DETERMINISTIC_HASHING` environment variable to `1`. (You only need to do
206+
this when running the benchmark executables directly -- the driver script does
207+
this for you automatically.)
208+
209+
* `$ env SWIFT_DETERMINISTIC_HASHING=1 ./Benchmark_O --num-iters=1 --num-samples=1`
210+
211+
This makes for more stable results, by preventing random hash collision changes
212+
from affecting benchmark measurements. Benchmark measurements start by checking
213+
that deterministic hashing is enabled and they fail with a runtime trap when it
214+
isn't.
215+
216+
If for some reason you want to run the benchmarks using standard randomized
217+
hashing, you can disable this check by passing the
218+
`--allow-nondeterministic-hashing` option to the executable.
219+
220+
* `$ ./Benchmark_O --num-iters=1 --num-samples=1 --allow-nondeterministic-hashing`
221+
222+
This will affect the reliability of measurements, so this is not recommended.
223+
224+
### Benchmarking by Numbers
225+
203226
As a shortcut, you can also refer to benchmarks by their ordinal numbers.
204227
These are printed out together with benchmark names and tags using the
205228
`--list` parameter. For a complete list of all available performance tests run

benchmark/cmake/modules/AddSwiftBenchmarkSuite.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ function (swift_benchmark_compile_archopts)
683683
${objcfile}
684684
"-o" "${OUTPUT_EXEC}"
685685
COMMAND
686-
"codesign" "-f" "-s" "-" "${OUTPUT_EXEC}")
686+
"${srcdir}/../utils/swift-darwin-postprocess.py" "${OUTPUT_EXEC}")
687687
else()
688688
# TODO: rpath.
689689
add_custom_command(

benchmark/scripts/Benchmark_Driver

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ class BenchmarkDriver(object):
5656
"""
5757
self.args = args
5858
self.run_env = os.environ.copy()
59+
60+
# Set a constant hash seed. Some tests are highly sensitive to
61+
# fluctuations in the number of hash collisions.
62+
self.run_env["SWIFT_DETERMINISTIC_HASHING"] = "1"
63+
5964
if hasattr(args, 'libdir') and args.libdir:
6065
# The benchmark binaries should pick up the built swift libraries
6166
# automatically, because their RPATH should point to ../lib/swift
@@ -65,15 +70,13 @@ class BenchmarkDriver(object):
6570
self.run_env["DYLD_LIBRARY_PATH"] = args.libdir
6671
elif platform.system() == "Linux":
6772
self.run_env["LD_LIBRARY_PATH"] = args.libdir
73+
6874
self._subprocess = _subprocess or subprocess
6975
self.all_tests = []
7076
self.test_number = {}
7177
self.tests = tests or self._get_tests()
7278
self.parser = parser or LogParser()
7379
self.results = {}
74-
# Set a constant hash seed. Some tests are currently sensitive to
75-
# fluctuations in the number of hash collisions.
76-
os.environ["SWIFT_DETERMINISTIC_HASHING"] = "1"
7780

7881
def _invoke(self, cmd):
7982
return self._subprocess.check_output(

benchmark/utils/DriverUtils.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ struct TestConfig {
104104
// Should we log the measurement metadata?
105105
let logMeta: Bool
106106

107+
// Allow running with nondeterministic hashing?
108+
var allowNondeterministicHashing: Bool
109+
107110
/// After we run the tests, should the harness sleep to allow for utilities
108111
/// like leaks that require a PID to run on the test harness.
109112
let afterRunSleep: UInt32?
@@ -128,6 +131,7 @@ struct TestConfig {
128131
var verbose: Bool?
129132
var logMemory: Bool?
130133
var logMeta: Bool?
134+
var allowNondeterministicHashing: Bool?
131135
var action: TestAction?
132136
var tests: [String]?
133137
}
@@ -191,6 +195,10 @@ struct TestConfig {
191195
p.addArgument("--list", \.action, defaultValue: .listTests,
192196
help: "don't run the tests, just log the list of test \n" +
193197
"numbers, names and tags (respects specified filters)")
198+
p.addArgument("--allow-nondeterministic-hashing",
199+
\.allowNondeterministicHashing, defaultValue: true,
200+
help: "Don't trap when running without the \n" +
201+
"SWIFT_DETERMINISTIC_HASHING=1 environment variable")
194202
p.addArgument(nil, \.tests) // positional arguments
195203

196204
let c = p.parse()
@@ -208,6 +216,7 @@ struct TestConfig {
208216
logMeta = c.logMeta ?? false
209217
afterRunSleep = c.afterRunSleep
210218
action = c.action ?? .run
219+
allowNondeterministicHashing = c.allowNondeterministicHashing ?? false
211220
tests = TestConfig.filterTests(registeredBenchmarks,
212221
tests: c.tests ?? [],
213222
tags: c.tags ?? [],
@@ -671,6 +680,17 @@ final class TestRunner {
671680
}
672681
}
673682

683+
extension Hasher {
684+
static var isDeterministic: Bool {
685+
// This is a quick test for deterministic hashing.
686+
// When hashing uses a random seed, each `Set` value
687+
// contains its members in some unique, random order.
688+
let set1 = Set(0 ..< 100)
689+
let set2 = Set(0 ..< 100)
690+
return set1.elementsEqual(set2)
691+
}
692+
}
693+
674694
public func main() {
675695
let config = TestConfig(registeredBenchmarks)
676696
switch (config.action) {
@@ -682,6 +702,18 @@ public func main() {
682702
print(testDescription)
683703
}
684704
case .run:
705+
if !config.allowNondeterministicHashing && !Hasher.isDeterministic {
706+
fatalError("""
707+
Benchmark runs require deterministic hashing to be enabled.
708+
709+
This prevents spurious regressions in hashed collection performance.
710+
You can do this by setting the SWIFT_DETERMINISTIC_HASHING environment
711+
variable to 1.
712+
713+
If you know what you're doing, you can disable this check by passing
714+
the option '--allow-nondeterministic-hashing to the benchmarking executable.
715+
""")
716+
}
685717
TestRunner(config).runBenchmarks()
686718
if let x = config.afterRunSleep {
687719
sleep(x)

utils/swift_build_support/swift_build_support/products/benchmarks.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,16 @@ def should_test(self, host_target):
5252
return self.args.test_toolchainbenchmarks
5353

5454
def _get_test_environment(self, host_target):
55+
env = {
56+
"SWIFT_DETERMINISTIC_HASHING": "1"
57+
}
5558
if platform.system() == 'Darwin':
5659
# the resulting binaries would search first in /usr/lib/swift,
5760
# we need to prefer the libraries we just built
58-
return {'DYLD_LIBRARY_PATH': os.path.join(
61+
env['DYLD_LIBRARY_PATH'] = os.path.join(
5962
_get_toolchain_path(host_target, self, self.args),
60-
'usr', 'lib', 'swift', 'macosx')}
61-
62-
return None
63+
'usr', 'lib', 'swift', 'macosx')
64+
return env
6365

6466
def test(self, host_target):
6567
"""Just run a single instance of the command for both .debug and

0 commit comments

Comments
 (0)