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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +130 -0
  4. data/Rakefile +20 -0
  5. data/exe/corkscrews +9 -0
  6. data/ext/corkscrews/corkscrews.c +24 -0
  7. data/ext/corkscrews/corkscrews_native.h +85 -0
  8. data/ext/corkscrews/delay.c +259 -0
  9. data/ext/corkscrews/extconf.rb +25 -0
  10. data/ext/corkscrews/hooks.c +156 -0
  11. data/ext/corkscrews/monitor.c +117 -0
  12. data/ext/corkscrews/record.c +109 -0
  13. data/ext/corkscrews/sampler.c +99 -0
  14. data/lib/corkscrews/analysis.rb +452 -0
  15. data/lib/corkscrews/boot.rb +25 -0
  16. data/lib/corkscrews/cli.rb +159 -0
  17. data/lib/corkscrews/controller.rb +137 -0
  18. data/lib/corkscrews/firefox_profile.rb +290 -0
  19. data/lib/corkscrews/native.rb +108 -0
  20. data/lib/corkscrews/primitives.rb +127 -0
  21. data/lib/corkscrews/rack.rb +39 -0
  22. data/lib/corkscrews/recorder.rb +903 -0
  23. data/lib/corkscrews/report.rb +241 -0
  24. data/lib/corkscrews/statistics.rb +112 -0
  25. data/lib/corkscrews/time_source.rb +11 -0
  26. data/lib/corkscrews/validate.rb +348 -0
  27. data/lib/corkscrews/version.rb +5 -0
  28. data/lib/corkscrews.rb +43 -0
  29. data/validate/benchmarks/b01_serial_hotspot/base.rb +28 -0
  30. data/validate/benchmarks/b01_serial_hotspot/manifest.yml +23 -0
  31. data/validate/benchmarks/b01_serial_hotspot/optimized.rb +28 -0
  32. data/validate/benchmarks/b02_false_hotspot/base.rb +43 -0
  33. data/validate/benchmarks/b02_false_hotspot/manifest.yml +19 -0
  34. data/validate/benchmarks/b02_false_hotspot/optimized.rb +43 -0
  35. data/validate/benchmarks/b03_io_wait/base.rb +29 -0
  36. data/validate/benchmarks/b03_io_wait/manifest.yml +16 -0
  37. data/validate/benchmarks/b03_io_wait/optimized.rb +29 -0
  38. data/validate/benchmarks/b04_lock_contention/base.rb +30 -0
  39. data/validate/benchmarks/b04_lock_contention/manifest.yml +19 -0
  40. data/validate/benchmarks/b04_lock_contention/optimized.rb +30 -0
  41. data/validate/benchmarks/b05_gvl_contention/base.rb +24 -0
  42. data/validate/benchmarks/b05_gvl_contention/manifest.yml +17 -0
  43. data/validate/benchmarks/b05_gvl_contention/optimized.rb +24 -0
  44. data/validate/benchmarks/b06_gc_pressure/base.rb +16 -0
  45. data/validate/benchmarks/b06_gc_pressure/manifest.yml +16 -0
  46. data/validate/benchmarks/b06_gc_pressure/optimized.rb +17 -0
  47. data/validate/benchmarks/b07_pipeline/base.rb +34 -0
  48. data/validate/benchmarks/b07_pipeline/manifest.yml +17 -0
  49. data/validate/benchmarks/b07_pipeline/optimized.rb +34 -0
  50. data/validate/benchmarks/b08_native_attribution/base.rb +18 -0
  51. data/validate/benchmarks/b08_native_attribution/manifest.yml +18 -0
  52. data/validate/benchmarks/b08_native_attribution/optimized.rb +18 -0
  53. data/validate/benchmarks/support.rb +29 -0
  54. data/validate/harness/run_all.rb +16 -0
  55. data/validate/harness/stats_check.py +27 -0
  56. data/validate/harness/t1.rb +15 -0
  57. data/validate/harness/t4.rb +15 -0
  58. data/validate/harness/t5.rb +15 -0
  59. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Corkscrews
4
+ VERSION = "0.1.0"
5
+ 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)