Skip to content

Commit a586263

Browse files
committed
Extract run_benchmarks to its own object, BenchmarkSuite
1 parent a088cc8 commit a586263

5 files changed

Lines changed: 680 additions & 292 deletions

File tree

lib/benchmark_runner.rb

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,6 @@ def free_file_no(directory)
1616
end
1717
end
1818

19-
# Resolve the pre_init file path into a form that can be required
20-
def expand_pre_init(path)
21-
require 'pathname'
22-
23-
path = Pathname.new(path)
24-
25-
unless path.exist?
26-
puts "--with-pre-init called with non-existent file!"
27-
exit(-1)
28-
end
29-
30-
if path.directory?
31-
puts "--with-pre-init called with a directory, please pass a .rb file"
32-
exit(-1)
33-
end
34-
35-
library_name = path.basename(path.extname)
36-
load_path = path.parent.expand_path
37-
38-
[
39-
"-I", load_path,
40-
"-r", library_name
41-
]
42-
end
43-
4419
# Sort benchmarks with headlines first, then others, then micro
4520
def sort_benchmarks(bench_names, metadata)
4621
headline_benchmarks = metadata.select { |_, meta| meta['category'] == 'headline' }.keys
@@ -51,36 +26,6 @@ def sort_benchmarks(bench_names, metadata)
5126
headline_names.sort + other_names.sort + micro_names.sort
5227
end
5328

54-
# Check which OS we are running
55-
def os
56-
@os ||= (
57-
host_os = RbConfig::CONFIG['host_os']
58-
case host_os
59-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
60-
:windows
61-
when /darwin|mac os/
62-
:macosx
63-
when /linux/
64-
:linux
65-
when /solaris|bsd/
66-
:unix
67-
else
68-
raise "unknown os: #{host_os.inspect}"
69-
end
70-
)
71-
end
72-
73-
# Generate setarch prefix for Linux
74-
def setarch_prefix
75-
# Disable address space randomization (for determinism)
76-
prefix = ["setarch", `uname -m`.strip, "-R"]
77-
78-
# Abort if we don't have permission (perhaps in a docker container).
79-
return [] unless system(*prefix, "true", out: File::NULL, err: File::NULL)
80-
81-
prefix
82-
end
83-
8429
# Checked system - error or return info if the command fails
8530
def check_call(command, env: {}, raise_error: true, quiet: false)
8631
puts("+ #{command}") unless quiet

lib/benchmark_suite.rb

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# frozen_string_literal: true
2+
3+
require 'json'
4+
require 'pathname'
5+
require 'fileutils'
6+
require 'shellwords'
7+
require 'etc'
8+
require 'yaml'
9+
require 'rbconfig'
10+
require_relative 'benchmark_filter'
11+
12+
# BenchmarkSuite runs a collection of benchmarks and collects their results
13+
class BenchmarkSuite
14+
attr_reader :ruby, :ruby_description, :categories, :name_filters, :out_path, :harness, :pre_init, :no_pinning
15+
16+
def initialize(ruby:, ruby_description:, categories:, name_filters:, out_path:, harness:, pre_init: nil, no_pinning: false)
17+
@ruby = ruby
18+
@ruby_description = ruby_description
19+
@categories = categories
20+
@name_filters = name_filters
21+
@out_path = out_path
22+
@harness = harness
23+
@pre_init = pre_init ? expand_pre_init(pre_init) : nil
24+
@no_pinning = no_pinning
25+
end
26+
27+
# Run all the benchmarks and record execution times
28+
# Returns [bench_data, bench_failures]
29+
def run
30+
bench_data = {}
31+
bench_failures = {}
32+
33+
bench_dir = "benchmarks"
34+
ractor_bench_dir = "benchmarks-ractor"
35+
36+
if categories == ["ractor-only"]
37+
bench_dir = ractor_bench_dir
38+
@harness = "harness-ractor"
39+
@categories = []
40+
end
41+
42+
bench_file_grouping = {}
43+
44+
# Get the list of benchmark files/directories matching name filters
45+
filter = benchmark_filter(categories: categories, name_filters: name_filters)
46+
bench_file_grouping[bench_dir] = Dir.children(bench_dir).sort.filter do |entry|
47+
filter.match?(entry)
48+
end
49+
50+
if categories == ["ractor"]
51+
# We ignore the category filter here because everything in the
52+
# benchmarks-ractor directory should be included when we're benchmarking the
53+
# Ractor category
54+
ractor_filter = benchmark_filter(categories: [], name_filters: name_filters)
55+
bench_file_grouping[ractor_bench_dir] = Dir.children(ractor_bench_dir).sort.filter do |entry|
56+
ractor_filter.match?(entry)
57+
end
58+
end
59+
60+
bench_file_grouping.each do |bench_dir, bench_files|
61+
bench_files.each_with_index do |entry, idx|
62+
bench_name = entry.gsub('.rb', '')
63+
64+
puts("Running benchmark \"#{bench_name}\" (#{idx+1}/#{bench_files.length})")
65+
66+
# Path to the benchmark runner script
67+
script_path = File.join(bench_dir, entry)
68+
69+
if !script_path.end_with?('.rb')
70+
script_path = File.join(script_path, 'benchmark.rb')
71+
end
72+
73+
# Set up the environment for the benchmarking command
74+
result_json_path = File.join(out_path, "temp#{Process.pid}.json")
75+
ENV["RESULT_JSON_PATH"] = result_json_path
76+
77+
# Set up the benchmarking command
78+
cmd = []
79+
if linux?
80+
cmd += setarch_prefix
81+
82+
# Pin the process to one given core to improve caching and reduce variance on CRuby
83+
# Other Rubies need to use multiple cores, e.g., for JIT threads
84+
if ruby_description.start_with?('ruby ') && !no_pinning
85+
# The last few cores of Intel CPU may be slow E-Cores, so avoid using the last one.
86+
cpu = [(Etc.nprocessors / 2) - 1, 0].max
87+
cmd += ["taskset", "-c", "#{cpu}"]
88+
end
89+
end
90+
91+
# Fix for jruby/jruby#7394 in JRuby 9.4.2.0
92+
script_path = File.expand_path(script_path)
93+
94+
cmd += [
95+
*ruby,
96+
"-I", harness,
97+
*pre_init,
98+
script_path,
99+
].compact
100+
101+
# When the Ruby running this script is not the first Ruby in PATH, shell commands
102+
# like `bundle install` in a child process will not use the Ruby being benchmarked.
103+
# It overrides PATH to guarantee the commands of the benchmarked Ruby will be used.
104+
env = {}
105+
ruby_path = `#{ruby.shelljoin} -e 'print RbConfig.ruby' 2> #{File::NULL}`
106+
if ruby_path != RbConfig.ruby
107+
env["PATH"] = "#{File.dirname(ruby_path)}:#{ENV["PATH"]}"
108+
109+
# chruby sets GEM_HOME and GEM_PATH in your shell. We have to unset it in the child
110+
# process to avoid installing gems to the version that is running run_benchmarks.rb.
111+
["GEM_HOME", "GEM_PATH"].each do |var|
112+
env[var] = nil if ENV.key?(var)
113+
end
114+
end
115+
116+
# Do the benchmarking
117+
result = BenchmarkRunner.check_call(cmd.shelljoin, env: env, raise_error: false)
118+
119+
if result[:success]
120+
bench_data[bench_name] = JSON.parse(File.read(result_json_path)).tap do |json|
121+
json["command_line"] = cmd.shelljoin
122+
File.unlink(result_json_path)
123+
end
124+
else
125+
bench_failures[bench_name] = result[:status].exitstatus
126+
end
127+
128+
end
129+
end
130+
131+
[bench_data, bench_failures]
132+
end
133+
134+
private
135+
136+
def benchmark_filter(categories:, name_filters:)
137+
@benchmark_filter ||= {}
138+
key = [categories, name_filters]
139+
@benchmark_filter[key] ||= BenchmarkFilter.new(
140+
categories: categories,
141+
name_filters: name_filters,
142+
metadata: benchmarks_metadata
143+
)
144+
end
145+
146+
def benchmarks_metadata
147+
@benchmarks_metadata ||= YAML.load_file('benchmarks.yml')
148+
end
149+
150+
# Check if running on Linux
151+
def linux?
152+
RbConfig::CONFIG['host_os'] =~ /linux/
153+
end
154+
155+
# Generate setarch prefix for Linux
156+
def setarch_prefix
157+
# Disable address space randomization (for determinism)
158+
prefix = ["setarch", `uname -m`.strip, "-R"]
159+
160+
# Abort if we don't have permission (perhaps in a docker container).
161+
return [] unless system(*prefix, "true", out: File::NULL, err: File::NULL)
162+
163+
prefix
164+
end
165+
166+
# Resolve the pre_init file path into a form that can be required
167+
def expand_pre_init(path)
168+
path = Pathname.new(path)
169+
170+
unless path.exist?
171+
puts "--with-pre-init called with non-existent file!"
172+
exit(-1)
173+
end
174+
175+
if path.directory?
176+
puts "--with-pre-init called with a directory, please pass a .rb file"
177+
exit(-1)
178+
end
179+
180+
library_name = path.basename(path.extname)
181+
load_path = path.parent.expand_path
182+
183+
[
184+
"-I", load_path,
185+
"-r", library_name
186+
]
187+
end
188+
end

0 commit comments

Comments
 (0)