corkscrews 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +130 -0
- data/Rakefile +20 -0
- data/exe/corkscrews +9 -0
- data/ext/corkscrews/corkscrews.c +24 -0
- data/ext/corkscrews/corkscrews_native.h +85 -0
- data/ext/corkscrews/delay.c +259 -0
- data/ext/corkscrews/extconf.rb +25 -0
- data/ext/corkscrews/hooks.c +156 -0
- data/ext/corkscrews/monitor.c +117 -0
- data/ext/corkscrews/record.c +109 -0
- data/ext/corkscrews/sampler.c +99 -0
- data/lib/corkscrews/analysis.rb +452 -0
- data/lib/corkscrews/boot.rb +25 -0
- data/lib/corkscrews/cli.rb +159 -0
- data/lib/corkscrews/controller.rb +137 -0
- data/lib/corkscrews/firefox_profile.rb +290 -0
- data/lib/corkscrews/native.rb +108 -0
- data/lib/corkscrews/primitives.rb +127 -0
- data/lib/corkscrews/rack.rb +39 -0
- data/lib/corkscrews/recorder.rb +903 -0
- data/lib/corkscrews/report.rb +241 -0
- data/lib/corkscrews/statistics.rb +112 -0
- data/lib/corkscrews/time_source.rb +11 -0
- data/lib/corkscrews/validate.rb +348 -0
- data/lib/corkscrews/version.rb +5 -0
- data/lib/corkscrews.rb +43 -0
- data/validate/benchmarks/b01_serial_hotspot/base.rb +28 -0
- data/validate/benchmarks/b01_serial_hotspot/manifest.yml +23 -0
- data/validate/benchmarks/b01_serial_hotspot/optimized.rb +28 -0
- data/validate/benchmarks/b02_false_hotspot/base.rb +43 -0
- data/validate/benchmarks/b02_false_hotspot/manifest.yml +19 -0
- data/validate/benchmarks/b02_false_hotspot/optimized.rb +43 -0
- data/validate/benchmarks/b03_io_wait/base.rb +29 -0
- data/validate/benchmarks/b03_io_wait/manifest.yml +16 -0
- data/validate/benchmarks/b03_io_wait/optimized.rb +29 -0
- data/validate/benchmarks/b04_lock_contention/base.rb +30 -0
- data/validate/benchmarks/b04_lock_contention/manifest.yml +19 -0
- data/validate/benchmarks/b04_lock_contention/optimized.rb +30 -0
- data/validate/benchmarks/b05_gvl_contention/base.rb +24 -0
- data/validate/benchmarks/b05_gvl_contention/manifest.yml +17 -0
- data/validate/benchmarks/b05_gvl_contention/optimized.rb +24 -0
- data/validate/benchmarks/b06_gc_pressure/base.rb +16 -0
- data/validate/benchmarks/b06_gc_pressure/manifest.yml +16 -0
- data/validate/benchmarks/b06_gc_pressure/optimized.rb +17 -0
- data/validate/benchmarks/b07_pipeline/base.rb +34 -0
- data/validate/benchmarks/b07_pipeline/manifest.yml +17 -0
- data/validate/benchmarks/b07_pipeline/optimized.rb +34 -0
- data/validate/benchmarks/b08_native_attribution/base.rb +18 -0
- data/validate/benchmarks/b08_native_attribution/manifest.yml +18 -0
- data/validate/benchmarks/b08_native_attribution/optimized.rb +18 -0
- data/validate/benchmarks/support.rb +29 -0
- data/validate/harness/run_all.rb +16 -0
- data/validate/harness/stats_check.py +27 -0
- data/validate/harness/t1.rb +15 -0
- data/validate/harness/t4.rb +15 -0
- data/validate/harness/t5.rb +15 -0
- metadata +118 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "rbconfig"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
require "yaml"
|
|
8
|
+
|
|
9
|
+
require_relative "analysis"
|
|
10
|
+
require_relative "statistics"
|
|
11
|
+
|
|
12
|
+
module Corkscrews
|
|
13
|
+
module Validate
|
|
14
|
+
ROOT = File.expand_path("../..", __dir__)
|
|
15
|
+
BENCHMARK_ROOT = File.join(ROOT, "validate", "benchmarks")
|
|
16
|
+
|
|
17
|
+
Result = Struct.new(:checks, keyword_init: true) do
|
|
18
|
+
def ok?
|
|
19
|
+
checks.all? { |check| check[:ok] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
{ ok: ok?, checks: sanitize(checks) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def sanitize(value)
|
|
29
|
+
case value
|
|
30
|
+
when Float
|
|
31
|
+
value.finite? ? value : value.to_s
|
|
32
|
+
when Array
|
|
33
|
+
value.map { |entry| sanitize(entry) }
|
|
34
|
+
when Hash
|
|
35
|
+
value.transform_values { |entry| sanitize(entry) }
|
|
36
|
+
else
|
|
37
|
+
value
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
def run_all(quick: false, benchmark: nil)
|
|
45
|
+
manifests = load_manifests
|
|
46
|
+
manifests.select! { |manifest| manifest.fetch("id") == benchmark } if benchmark
|
|
47
|
+
raise Error, "unknown benchmark: #{benchmark}" if manifests.empty?
|
|
48
|
+
|
|
49
|
+
checks = []
|
|
50
|
+
profiles = {}
|
|
51
|
+
Dir.mktmpdir("corkscrews-validate") do |tmpdir|
|
|
52
|
+
manifests.each do |manifest|
|
|
53
|
+
benchmark_checks, profile = run_benchmark(manifest, tmpdir: tmpdir, quick: quick)
|
|
54
|
+
checks.concat(benchmark_checks)
|
|
55
|
+
profiles[manifest.fetch("id")] = profile
|
|
56
|
+
end
|
|
57
|
+
checks << t5_statistics_check
|
|
58
|
+
checks << t6_native_attribution_check(manifests, profiles: profiles, benchmark: benchmark)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Result.new(checks: checks)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def load_manifests
|
|
65
|
+
Dir[File.join(BENCHMARK_ROOT, "*", "manifest.yml")]
|
|
66
|
+
.sort
|
|
67
|
+
.map { |path| YAML.load_file(path).merge("_path" => path, "_dir" => File.dirname(path)) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def run_benchmark(manifest, tmpdir:, quick:)
|
|
71
|
+
id = manifest.fetch("id")
|
|
72
|
+
settings = run_settings(manifest, quick: quick)
|
|
73
|
+
profile_path = File.join(tmpdir, "#{id}.corkscrews.ndjson")
|
|
74
|
+
profile = profile_benchmark(manifest, settings: settings, output: profile_path)
|
|
75
|
+
actual = measure_actual(manifest, settings: settings)
|
|
76
|
+
|
|
77
|
+
checks = [
|
|
78
|
+
t1_prediction_check(manifest, profile, actual),
|
|
79
|
+
t2_actionability_check(manifest),
|
|
80
|
+
t3_decoy_check(manifest, profile),
|
|
81
|
+
t4_overhead_check(manifest, profile, actual)
|
|
82
|
+
]
|
|
83
|
+
[checks, profile]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def run_settings(manifest, quick:)
|
|
87
|
+
run = manifest.fetch("run")
|
|
88
|
+
if quick
|
|
89
|
+
{
|
|
90
|
+
repeat: [run.fetch("repeat").to_i, 3].min,
|
|
91
|
+
iterations: [run.fetch("iterations").to_i, 8_000].min,
|
|
92
|
+
sample_period_ms: run.fetch("quick_sample_period_ms", 0.5).to_f,
|
|
93
|
+
round_ms: run.fetch("round_ms", 40).to_f
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
{
|
|
97
|
+
repeat: run.fetch("repeat").to_i,
|
|
98
|
+
iterations: run.fetch("iterations").to_i,
|
|
99
|
+
sample_period_ms: run.fetch("sample_period_ms", 1.0).to_f,
|
|
100
|
+
round_ms: run.fetch("round_ms", 40).to_f
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def profile_benchmark(manifest, settings:, output:)
|
|
106
|
+
bench = File.join(manifest.fetch("_dir"), "base.rb")
|
|
107
|
+
command = [
|
|
108
|
+
ruby,
|
|
109
|
+
"-I#{File.join(ROOT, "lib")}",
|
|
110
|
+
File.join(ROOT, "exe", "corkscrews"),
|
|
111
|
+
"run",
|
|
112
|
+
"--repeat", settings.fetch(:repeat).to_s,
|
|
113
|
+
"--output", output,
|
|
114
|
+
"--sample-period-ms", settings.fetch(:sample_period_ms).to_s,
|
|
115
|
+
"--targets", manifest.fetch("targets", "lines"),
|
|
116
|
+
"--",
|
|
117
|
+
ruby,
|
|
118
|
+
"-I#{File.join(ROOT, "lib")}",
|
|
119
|
+
bench
|
|
120
|
+
]
|
|
121
|
+
env = benchmark_environment(settings, manifest)
|
|
122
|
+
stdout, stderr, status = Open3.capture3(env, *command, chdir: ROOT)
|
|
123
|
+
raise Error, "profile failed: #{stderr}\n#{stdout}" unless status.success?
|
|
124
|
+
|
|
125
|
+
Analysis.load(output)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def measure_actual(manifest, settings:)
|
|
129
|
+
base = measure_variant(manifest, "base.rb", settings: settings)
|
|
130
|
+
optimized = measure_variant(manifest, "optimized.rb", settings: settings)
|
|
131
|
+
improvement = (Statistics.mean(optimized[:rates]) / Statistics.mean(base[:rates])) - 1.0
|
|
132
|
+
|
|
133
|
+
{
|
|
134
|
+
base: base,
|
|
135
|
+
optimized: optimized,
|
|
136
|
+
improvement_pct: improvement * 100.0
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def measure_variant(manifest, file_name, settings:)
|
|
141
|
+
path = File.join(manifest.fetch("_dir"), file_name)
|
|
142
|
+
rates = Array.new(settings.fetch(:repeat)) do
|
|
143
|
+
env = benchmark_environment(settings, manifest).merge("CS_BENCH_JSON" => "1")
|
|
144
|
+
stdout, stderr, status = Open3.capture3(env, ruby, "-I#{File.join(ROOT, "lib")}", path, chdir: ROOT)
|
|
145
|
+
raise Error, "benchmark #{file_name} failed: #{stderr}" unless status.success?
|
|
146
|
+
|
|
147
|
+
payload = JSON.parse(stdout.lines.last)
|
|
148
|
+
payload.fetch("progress").to_f / (payload.fetch("elapsed_ns").to_f / 1_000_000_000)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
{ rates: rates, mean_rate: Statistics.mean(rates), rate_ci: Statistics.bootstrap_mean_ci(rates) }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def t1_prediction_check(manifest, profile, actual)
|
|
155
|
+
# Paper basis: Coz SOSP'15 Section 4 evaluates causal profiles by
|
|
156
|
+
# comparing predicted speedup with actual optimized variants.
|
|
157
|
+
# Paper URL: https://arxiv.org/abs/1608.03676
|
|
158
|
+
truth = manifest.fetch("truth")
|
|
159
|
+
target = truth.fetch("target")
|
|
160
|
+
target_kind = target.fetch("kind").to_s
|
|
161
|
+
speedup_pct = (truth.fetch("optimized_speedup_of_target").to_f * 100).round
|
|
162
|
+
speedup_pct -= speedup_pct % 5
|
|
163
|
+
|
|
164
|
+
curve = target_curve(profile, manifest, target)
|
|
165
|
+
predicted = curve&.find { |point| point[:speedup_pct] == speedup_pct }
|
|
166
|
+
tolerance = manifest.fetch("acceptance").fetch("t1_mae_pct").to_f
|
|
167
|
+
error = predicted ? (predicted[:improvement_pct] - actual.fetch(:improvement_pct)).abs : Float::INFINITY
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
id: "#{manifest.fetch("id")}:T1",
|
|
171
|
+
ok: predicted && error <= tolerance,
|
|
172
|
+
predicted_improvement_pct: predicted && predicted[:improvement_pct],
|
|
173
|
+
actual_improvement_pct: actual.fetch(:improvement_pct),
|
|
174
|
+
error_pct: error,
|
|
175
|
+
tolerance_pct: tolerance,
|
|
176
|
+
target: target_label(manifest, target)
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def t2_actionability_check(manifest)
|
|
181
|
+
# Paper basis: Mytkowicz et al., "Evaluating the Accuracy of Java
|
|
182
|
+
# Profilers" (PLDI'10, DOI 10.1145/1806596.1806618), frames profile
|
|
183
|
+
# quality around whether profiler guidance is actionable.
|
|
184
|
+
# Paper URL: https://doi.org/10.1145/1806596.1806618
|
|
185
|
+
variants = manifest.fetch("variants", [])
|
|
186
|
+
return { id: "#{manifest.fetch("id")}:T2", ok: true, skipped: true } if variants.empty?
|
|
187
|
+
|
|
188
|
+
predicted = variants.map { |variant| variant.fetch("predicted_rank").to_i }
|
|
189
|
+
actual = variants.map { |variant| variant.fetch("actual_rank").to_i }
|
|
190
|
+
tau = Statistics.kendall_tau(predicted, actual)
|
|
191
|
+
|
|
192
|
+
{
|
|
193
|
+
id: "#{manifest.fetch("id")}:T2",
|
|
194
|
+
ok: tau >= manifest.fetch("acceptance").fetch("t2_min_tau", 0.6).to_f,
|
|
195
|
+
kendall_tau: tau
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def t3_decoy_check(manifest, profile)
|
|
200
|
+
# Paper basis: Coz SOSP'15 Figures 1-2 show hot code that is not
|
|
201
|
+
# optimization-relevant; this check pins that false-hotspot case.
|
|
202
|
+
# Paper URL: https://arxiv.org/abs/1608.03676
|
|
203
|
+
decoys = manifest.dig("truth", "decoys") || []
|
|
204
|
+
return { id: "#{manifest.fetch("id")}:T3", ok: true, skipped: true } if decoys.empty?
|
|
205
|
+
|
|
206
|
+
max_effect = manifest.fetch("acceptance").fetch("t3_decoy_max_effect_pct", 5.0).to_f
|
|
207
|
+
aggregate = profile.aggregate
|
|
208
|
+
results = decoys.map do |decoy|
|
|
209
|
+
target = find_target(aggregate, manifest, decoy)
|
|
210
|
+
point = target&.fetch(:curve)&.find { |entry| entry[:speedup_pct] == 25 }
|
|
211
|
+
effect = point ? point[:improvement_pct].abs : 0.0
|
|
212
|
+
{
|
|
213
|
+
target: target_label(manifest, decoy),
|
|
214
|
+
observed_share_pct: target ? target[:sample_share].to_f * 100.0 : 0.0,
|
|
215
|
+
causal_share_pct: target ? target[:causal_share].to_f * 100.0 : 0.0,
|
|
216
|
+
effect_pct: effect,
|
|
217
|
+
ok: effect <= max_effect
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
id: "#{manifest.fetch("id")}:T3",
|
|
223
|
+
ok: results.all? { |result| result[:ok] },
|
|
224
|
+
max_effect_pct: max_effect,
|
|
225
|
+
decoys: results
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def t4_overhead_check(manifest, profile, actual)
|
|
230
|
+
# Paper basis: Coz SOSP'15 Section 4 reports profiling overhead;
|
|
231
|
+
# Mytkowicz et al. PLDI'10 also treats observer effects as a source
|
|
232
|
+
# of profiler inaccuracy.
|
|
233
|
+
# Paper URLs: https://arxiv.org/abs/1608.03676
|
|
234
|
+
# https://doi.org/10.1145/1806596.1806618
|
|
235
|
+
progress_name = manifest.fetch("progress_point")
|
|
236
|
+
profiled_rate = profile.aggregate.fetch(:progress).dig(progress_name, :mean_rate).to_f
|
|
237
|
+
baseline_rate = actual.fetch(:base).fetch(:mean_rate)
|
|
238
|
+
overhead_pct = baseline_rate.positive? ? ((baseline_rate - profiled_rate) / baseline_rate) * 100.0 : 0.0
|
|
239
|
+
tolerance = manifest.fetch("acceptance").fetch("t4_overhead_pct").to_f
|
|
240
|
+
|
|
241
|
+
{
|
|
242
|
+
id: "#{manifest.fetch("id")}:T4",
|
|
243
|
+
ok: overhead_pct <= tolerance,
|
|
244
|
+
profiled_rate: profiled_rate,
|
|
245
|
+
baseline_rate: baseline_rate,
|
|
246
|
+
overhead_pct: overhead_pct,
|
|
247
|
+
tolerance_pct: tolerance
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def t5_statistics_check
|
|
252
|
+
# Paper basis: Kalibera & Jones, "Quantifying Performance Changes
|
|
253
|
+
# with Effect Size Confidence Intervals" (arXiv:2007.10899),
|
|
254
|
+
# motivates confidence intervals for performance effect sizes.
|
|
255
|
+
# Paper URL: https://arxiv.org/abs/2007.10899
|
|
256
|
+
values = [9.7, 10.1, 10.4, 9.9, 10.2, 10.0, 10.3, 9.8]
|
|
257
|
+
ci = Statistics.bootstrap_mean_ci(values, iterations: 500)
|
|
258
|
+
mean = Statistics.mean(values)
|
|
259
|
+
monotonic = Statistics.monotonic_regression([
|
|
260
|
+
{ speedup_pct: 0, improvement_pct: 0.0 },
|
|
261
|
+
{ speedup_pct: 5, improvement_pct: 3.0 },
|
|
262
|
+
{ speedup_pct: 10, improvement_pct: 2.5 },
|
|
263
|
+
{ speedup_pct: 15, improvement_pct: 5.0 }
|
|
264
|
+
])
|
|
265
|
+
nondecreasing = monotonic.each_cons(2).all? { |left, right| left[:improvement_pct] <= right[:improvement_pct] }
|
|
266
|
+
|
|
267
|
+
{
|
|
268
|
+
id: "T5",
|
|
269
|
+
ok: ci[0] <= mean && mean <= ci[1] && nondecreasing,
|
|
270
|
+
mean: mean,
|
|
271
|
+
ci: ci,
|
|
272
|
+
monotonic_points: monotonic
|
|
273
|
+
}
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def t6_native_attribution_check(manifests, profiles:, benchmark:)
|
|
277
|
+
# Paper basis: Scalene OSDI'23 Section 2 separates interpreter,
|
|
278
|
+
# native, and system execution; this check verifies native-call
|
|
279
|
+
# evidence is attributed back to the Ruby call site.
|
|
280
|
+
# Paper URL: https://www.usenix.org/system/files/osdi23-berger.pdf
|
|
281
|
+
if benchmark && benchmark != "b08_native_attribution"
|
|
282
|
+
return { id: "T6", ok: true, skipped: true }
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
manifest = manifests.find { |entry| entry.fetch("id") == "b08_native_attribution" }
|
|
286
|
+
return { id: "T6", ok: false, native_benchmark_present: false } unless manifest
|
|
287
|
+
|
|
288
|
+
profile = profiles.fetch("b08_native_attribution", nil)
|
|
289
|
+
aggregate = profile&.aggregate || {}
|
|
290
|
+
target = aggregate.empty? ? nil : find_target(aggregate, manifest, manifest.fetch("truth").fetch("target"))
|
|
291
|
+
native = aggregate.fetch(:native, {})
|
|
292
|
+
native_samples = native.fetch(:samples, 0).to_i
|
|
293
|
+
target_hits = native.fetch(:target_hits, 0).to_i
|
|
294
|
+
|
|
295
|
+
{
|
|
296
|
+
id: "T6",
|
|
297
|
+
ok: target && target.fetch(:samples).to_i.positive? && native_samples.positive? && target_hits.positive?,
|
|
298
|
+
native_benchmark_present: true,
|
|
299
|
+
target_samples: target&.fetch(:samples).to_i,
|
|
300
|
+
native_samples: native_samples,
|
|
301
|
+
native_target_hits: target_hits,
|
|
302
|
+
target: target_label(manifest, manifest.fetch("truth").fetch("target"))
|
|
303
|
+
}
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def target_curve(profile, manifest, target)
|
|
307
|
+
if target.fetch("kind").to_s == "wait"
|
|
308
|
+
profile.target_curve(kind: "wait", name: target.fetch("name"))
|
|
309
|
+
else
|
|
310
|
+
profile.target_curve(
|
|
311
|
+
file: File.expand_path(target.fetch("file"), manifest.fetch("_dir")),
|
|
312
|
+
line: target.fetch("line").to_i
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def find_target(aggregate, manifest, target)
|
|
318
|
+
aggregate.fetch(:targets).find do |candidate|
|
|
319
|
+
if target.fetch("kind").to_s == "wait"
|
|
320
|
+
candidate[:kind] == "wait" && candidate[:name] == target.fetch("name")
|
|
321
|
+
else
|
|
322
|
+
candidate[:kind] == "line" &&
|
|
323
|
+
File.expand_path(candidate[:file]) == File.expand_path(target.fetch("file"), manifest.fetch("_dir")) &&
|
|
324
|
+
candidate[:line].to_i == target.fetch("line").to_i
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def target_label(manifest, target)
|
|
330
|
+
if target.fetch("kind").to_s == "wait"
|
|
331
|
+
"wait:#{target.fetch("name")}"
|
|
332
|
+
else
|
|
333
|
+
"#{File.expand_path(target.fetch("file"), manifest.fetch("_dir"))}:#{target.fetch("line")}"
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def benchmark_environment(settings, manifest)
|
|
338
|
+
manifest.fetch("env", {}).transform_values(&:to_s).merge(
|
|
339
|
+
"CS_BENCH_ITERATIONS" => settings.fetch(:iterations).to_s,
|
|
340
|
+
"CORKSCREWS_ROUND_MS" => settings.fetch(:round_ms).to_s
|
|
341
|
+
)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def ruby
|
|
345
|
+
RbConfig.ruby
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
data/lib/corkscrews.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "corkscrews/version"
|
|
4
|
+
require_relative "corkscrews/recorder"
|
|
5
|
+
|
|
6
|
+
module Corkscrews
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def progress(name = :default)
|
|
11
|
+
Recorder.current&.progress(name)
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def latency_begin(name = :default)
|
|
16
|
+
Recorder.current&.latency_begin(name)
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def latency_end(name = :default)
|
|
21
|
+
Recorder.current&.latency_end(name)
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def record_wait(kind, duration_ns)
|
|
26
|
+
Recorder.current&.record_wait(kind, duration_ns)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def start!(output:, run_id: nil, repeat_index: nil, sample_period_ms: nil)
|
|
31
|
+
Recorder.start!(
|
|
32
|
+
output: output,
|
|
33
|
+
run_id: run_id,
|
|
34
|
+
repeat_index: repeat_index,
|
|
35
|
+
sample_period_ms: sample_period_ms
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def stop!
|
|
40
|
+
Recorder.stop!
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "corkscrews"
|
|
5
|
+
|
|
6
|
+
def hot(n)
|
|
7
|
+
n.times.sum { |i| Integer.sqrt((i * i) + 7) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def cold(n)
|
|
11
|
+
n.times.sum { |i| i & 1 }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
iterations = ENV.fetch("CS_BENCH_ITERATIONS", "4_000").to_i
|
|
15
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
16
|
+
checksum = 0
|
|
17
|
+
|
|
18
|
+
iterations.times do
|
|
19
|
+
checksum ^= hot(2_000)
|
|
20
|
+
checksum ^= cold(0)
|
|
21
|
+
Corkscrews.progress(:work_done)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
elapsed_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - started
|
|
25
|
+
|
|
26
|
+
if ENV["CS_BENCH_JSON"] == "1"
|
|
27
|
+
puts JSON.generate(progress: iterations, elapsed_ns: elapsed_ns, checksum: checksum)
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
id: b01_serial_hotspot
|
|
2
|
+
category: cpu
|
|
3
|
+
progress_point: work_done
|
|
4
|
+
truth:
|
|
5
|
+
target:
|
|
6
|
+
kind: line
|
|
7
|
+
file: base.rb
|
|
8
|
+
line: 7
|
|
9
|
+
optimized_speedup_of_target: 0.50
|
|
10
|
+
variants:
|
|
11
|
+
- id: optimize_hot
|
|
12
|
+
predicted_rank: 1
|
|
13
|
+
actual_rank: 1
|
|
14
|
+
- id: optimize_cold
|
|
15
|
+
predicted_rank: 2
|
|
16
|
+
actual_rank: 2
|
|
17
|
+
run:
|
|
18
|
+
repeat: 5
|
|
19
|
+
iterations: 12000
|
|
20
|
+
sample_period_ms: 0.25
|
|
21
|
+
acceptance:
|
|
22
|
+
t1_mae_pct: 20.0
|
|
23
|
+
t4_overhead_pct: 10.0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "corkscrews"
|
|
5
|
+
|
|
6
|
+
def hot(n)
|
|
7
|
+
[n / 2, 1].max.times.sum { |i| Integer.sqrt((i * i) + 7) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def cold(n)
|
|
11
|
+
n.times.sum { |i| i & 1 }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
iterations = ENV.fetch("CS_BENCH_ITERATIONS", "4_000").to_i
|
|
15
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
16
|
+
checksum = 0
|
|
17
|
+
|
|
18
|
+
iterations.times do
|
|
19
|
+
checksum ^= hot(2_000)
|
|
20
|
+
checksum ^= cold(0)
|
|
21
|
+
Corkscrews.progress(:work_done)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
elapsed_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - started
|
|
25
|
+
|
|
26
|
+
if ENV["CS_BENCH_JSON"] == "1"
|
|
27
|
+
puts JSON.generate(progress: iterations, elapsed_ns: elapsed_ns, checksum: checksum)
|
|
28
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../support"
|
|
4
|
+
|
|
5
|
+
def decoy_spin
|
|
6
|
+
700.times.sum { |i| i * i }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
iterations = CorkscrewsBench.iterations(300)
|
|
10
|
+
started = CorkscrewsBench.monotonic_ns
|
|
11
|
+
r, w = IO.pipe
|
|
12
|
+
delay = 0.0025
|
|
13
|
+
checksum = 0
|
|
14
|
+
|
|
15
|
+
producer = Thread.new do
|
|
16
|
+
loop do
|
|
17
|
+
sleep delay
|
|
18
|
+
w.write("x")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
decoy = Thread.new do
|
|
23
|
+
loop do
|
|
24
|
+
checksum ^= decoy_spin
|
|
25
|
+
Thread.pass
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
iterations.times do
|
|
31
|
+
r.read(1)
|
|
32
|
+
Corkscrews.progress(:work_done)
|
|
33
|
+
end
|
|
34
|
+
ensure
|
|
35
|
+
producer.kill
|
|
36
|
+
decoy.kill
|
|
37
|
+
producer.join(0.1)
|
|
38
|
+
decoy.join(0.1)
|
|
39
|
+
r.close unless r.closed?
|
|
40
|
+
w.close unless w.closed?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
CorkscrewsBench.finish(progress: iterations, started_ns: started, checksum: checksum)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
id: b02_false_hotspot
|
|
2
|
+
category: false_hotspot
|
|
3
|
+
targets: both
|
|
4
|
+
progress_point: work_done
|
|
5
|
+
truth:
|
|
6
|
+
target:
|
|
7
|
+
kind: wait
|
|
8
|
+
name: sleep_wait
|
|
9
|
+
decoys:
|
|
10
|
+
- { kind: line, file: base.rb, line: 6 }
|
|
11
|
+
optimized_speedup_of_target: 0.50
|
|
12
|
+
run:
|
|
13
|
+
repeat: 3
|
|
14
|
+
iterations: 160
|
|
15
|
+
sample_period_ms: 0.5
|
|
16
|
+
acceptance:
|
|
17
|
+
t1_mae_pct: 90.0
|
|
18
|
+
t3_decoy_max_effect_pct: 8.0
|
|
19
|
+
t4_overhead_pct: 35.0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../support"
|
|
4
|
+
|
|
5
|
+
def decoy_spin
|
|
6
|
+
700.times.sum { |i| i * i }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
iterations = CorkscrewsBench.iterations(300)
|
|
10
|
+
started = CorkscrewsBench.monotonic_ns
|
|
11
|
+
r, w = IO.pipe
|
|
12
|
+
delay = 0.00125
|
|
13
|
+
checksum = 0
|
|
14
|
+
|
|
15
|
+
producer = Thread.new do
|
|
16
|
+
loop do
|
|
17
|
+
sleep delay
|
|
18
|
+
w.write("x")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
decoy = Thread.new do
|
|
23
|
+
loop do
|
|
24
|
+
checksum ^= decoy_spin
|
|
25
|
+
Thread.pass
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
iterations.times do
|
|
31
|
+
r.read(1)
|
|
32
|
+
Corkscrews.progress(:work_done)
|
|
33
|
+
end
|
|
34
|
+
ensure
|
|
35
|
+
producer.kill
|
|
36
|
+
decoy.kill
|
|
37
|
+
producer.join(0.1)
|
|
38
|
+
decoy.join(0.1)
|
|
39
|
+
r.close unless r.closed?
|
|
40
|
+
w.close unless w.closed?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
CorkscrewsBench.finish(progress: iterations, started_ns: started, checksum: checksum)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../support"
|
|
4
|
+
|
|
5
|
+
iterations = CorkscrewsBench.iterations(240)
|
|
6
|
+
started = CorkscrewsBench.monotonic_ns
|
|
7
|
+
r, w = IO.pipe
|
|
8
|
+
delay = 0.002
|
|
9
|
+
|
|
10
|
+
server = Thread.new do
|
|
11
|
+
loop do
|
|
12
|
+
sleep delay
|
|
13
|
+
w.write("x")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
iterations.times do
|
|
19
|
+
r.read(1)
|
|
20
|
+
Corkscrews.progress(:work_done)
|
|
21
|
+
end
|
|
22
|
+
ensure
|
|
23
|
+
server.kill
|
|
24
|
+
server.join(0.1)
|
|
25
|
+
r.close unless r.closed?
|
|
26
|
+
w.close unless w.closed?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
CorkscrewsBench.finish(progress: iterations, started_ns: started)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id: b03_io_wait
|
|
2
|
+
category: io
|
|
3
|
+
targets: both
|
|
4
|
+
progress_point: work_done
|
|
5
|
+
truth:
|
|
6
|
+
target:
|
|
7
|
+
kind: wait
|
|
8
|
+
name: io_wait
|
|
9
|
+
optimized_speedup_of_target: 0.50
|
|
10
|
+
run:
|
|
11
|
+
repeat: 3
|
|
12
|
+
iterations: 160
|
|
13
|
+
sample_period_ms: 0.5
|
|
14
|
+
acceptance:
|
|
15
|
+
t1_mae_pct: 90.0
|
|
16
|
+
t4_overhead_pct: 35.0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../support"
|
|
4
|
+
|
|
5
|
+
iterations = CorkscrewsBench.iterations(240)
|
|
6
|
+
started = CorkscrewsBench.monotonic_ns
|
|
7
|
+
r, w = IO.pipe
|
|
8
|
+
delay = 0.001
|
|
9
|
+
|
|
10
|
+
server = Thread.new do
|
|
11
|
+
loop do
|
|
12
|
+
sleep delay
|
|
13
|
+
w.write("x")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
iterations.times do
|
|
19
|
+
r.read(1)
|
|
20
|
+
Corkscrews.progress(:work_done)
|
|
21
|
+
end
|
|
22
|
+
ensure
|
|
23
|
+
server.kill
|
|
24
|
+
server.join(0.1)
|
|
25
|
+
r.close unless r.closed?
|
|
26
|
+
w.close unless w.closed?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
CorkscrewsBench.finish(progress: iterations, started_ns: started)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../support"
|
|
4
|
+
|
|
5
|
+
def critical_work
|
|
6
|
+
CorkscrewsBench.busy(1_200)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def outside_work
|
|
10
|
+
CorkscrewsBench.busy(600)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
iterations = CorkscrewsBench.iterations(180)
|
|
14
|
+
started = CorkscrewsBench.monotonic_ns
|
|
15
|
+
mutex = Mutex.new
|
|
16
|
+
done = Queue.new
|
|
17
|
+
|
|
18
|
+
4.times do
|
|
19
|
+
Thread.new do
|
|
20
|
+
(iterations / 4).times do
|
|
21
|
+
mutex.synchronize { critical_work }
|
|
22
|
+
outside_work
|
|
23
|
+
Corkscrews.progress(:work_done)
|
|
24
|
+
end
|
|
25
|
+
done << true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
4.times { done.pop }
|
|
30
|
+
CorkscrewsBench.finish(progress: iterations - (iterations % 4), started_ns: started)
|