Skip to content

Commit 32f9e44

Browse files
authored
Merge pull request #435 from ruby/rmf-cpu-logic
Extract CPU configuration logic into its own file
2 parents 15ad5ec + 68a4f3b commit 32f9e44

File tree

5 files changed

+625
-99
lines changed

5 files changed

+625
-99
lines changed

lib/benchmark_runner.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,21 @@ def setarch_prefix
8080

8181
prefix
8282
end
83+
84+
# Checked system - error or return info if the command fails
85+
def check_call(command, env: {}, raise_error: true, quiet: false)
86+
puts("+ #{command}") unless quiet
87+
88+
result = {}
89+
90+
result[:success] = system(env, command)
91+
result[:status] = $?
92+
93+
unless result[:success]
94+
puts "Command #{command.inspect} failed with exit code #{result[:status].exitstatus} in directory #{Dir.pwd}"
95+
raise RuntimeError.new if raise_error
96+
end
97+
98+
result
99+
end
83100
end

lib/cpu_config.rb

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
require_relative 'benchmark_runner'
2+
3+
# Manages CPU frequency and turbo boost configuration for benchmark consistency
4+
class CPUConfig
5+
class << self
6+
# Configure CPU for benchmarking: disable frequency scaling and verify settings
7+
def configure_for_benchmarking(turbo:)
8+
build.configure_for_benchmarking(turbo: turbo)
9+
end
10+
11+
def build
12+
if File.exist?(IntelCPUConfig::PSTATE_DIR)
13+
IntelCPUConfig.new
14+
elsif File.exist?(AMDCPUConfig::BOOST_PATH)
15+
AMDCPUConfig.new
16+
else
17+
NullCPUConfig.new
18+
end
19+
end
20+
end
21+
22+
def configure_for_benchmarking(turbo:)
23+
disable_frequency_scaling(turbo: turbo)
24+
check_pstate(turbo: turbo)
25+
end
26+
27+
private
28+
29+
def disable_frequency_scaling(turbo:)
30+
disable_turbo_boost unless turbo || turbo_disabled?
31+
maximize_frequency unless frequency_maximized?
32+
end
33+
34+
def turbo_disabled?
35+
# Override in subclasses
36+
false
37+
end
38+
39+
def frequency_maximized?
40+
# Override in subclasses
41+
false
42+
end
43+
44+
def disable_turbo_boost
45+
# Override in subclasses
46+
end
47+
48+
def maximize_frequency
49+
# Override in subclasses
50+
end
51+
52+
def check_pstate(turbo:)
53+
# Override in subclasses
54+
end
55+
end
56+
57+
# Intel CPU configuration
58+
class IntelCPUConfig < CPUConfig
59+
PSTATE_DIR = '/sys/devices/system/cpu/intel_pstate'
60+
NO_TURBO_PATH = "#{PSTATE_DIR}/no_turbo"
61+
MIN_PERF_PCT_PATH = "#{PSTATE_DIR}/min_perf_pct"
62+
TURBO_DISABLED_VALUE = '1'
63+
FREQUENCY_MAXIMIZED_VALUE = '100'
64+
65+
private
66+
67+
def disable_turbo_boost
68+
# sudo requires the flag '-S' in order to take input from stdin
69+
BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_DISABLED_VALUE} > #{NO_TURBO_PATH}'")
70+
at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > #{NO_TURBO_PATH}'", quiet: true) }
71+
end
72+
73+
def maximize_frequency
74+
# Disabling Turbo Boost reduces the CPU frequency, so this should be run after that.
75+
BenchmarkRunner.check_call("sudo -S sh -c 'echo #{FREQUENCY_MAXIMIZED_VALUE} > #{MIN_PERF_PCT_PATH}'")
76+
end
77+
78+
def turbo_disabled?
79+
@turbo_disabled ||= File.exist?(NO_TURBO_PATH) &&
80+
File.read(NO_TURBO_PATH).strip == TURBO_DISABLED_VALUE
81+
end
82+
83+
def frequency_maximized?
84+
@frequency_maximized ||= File.exist?(MIN_PERF_PCT_PATH) &&
85+
File.read(MIN_PERF_PCT_PATH).strip == FREQUENCY_MAXIMIZED_VALUE
86+
end
87+
88+
def check_pstate(turbo:)
89+
unless turbo || turbo_disabled?
90+
puts("You forgot to disable turbo:")
91+
puts(" sudo sh -c 'echo #{TURBO_DISABLED_VALUE} > #{NO_TURBO_PATH}'")
92+
exit(-1)
93+
end
94+
95+
unless frequency_maximized?
96+
puts("You forgot to set the min perf percentage to 100:")
97+
puts(" sudo sh -c 'echo #{FREQUENCY_MAXIMIZED_VALUE} > #{MIN_PERF_PCT_PATH}'")
98+
exit(-1)
99+
end
100+
end
101+
end
102+
103+
# AMD CPU configuration
104+
class AMDCPUConfig < CPUConfig
105+
CPUFREQ_DIR = '/sys/devices/system/cpu/cpufreq'
106+
BOOST_PATH = "#{CPUFREQ_DIR}/boost"
107+
SCALING_GOVERNOR_GLOB = '/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor'
108+
TURBO_DISABLED_VALUE = '0'
109+
TURBO_ENABLED_VALUE = '1'
110+
PERFORMANCE_GOVERNOR = 'performance'
111+
112+
private
113+
114+
def disable_turbo_boost
115+
# sudo requires the flag '-S' in order to take input from stdin
116+
BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_DISABLED_VALUE} > #{BOOST_PATH}'")
117+
at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_ENABLED_VALUE} > #{BOOST_PATH}'", quiet: true) }
118+
end
119+
120+
def maximize_frequency
121+
BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance")
122+
end
123+
124+
def turbo_disabled?
125+
@turbo_disabled ||= File.exist?(BOOST_PATH) &&
126+
File.read(BOOST_PATH).strip == TURBO_DISABLED_VALUE
127+
end
128+
129+
def frequency_maximized?
130+
@frequency_maximized ||= Dir.glob(SCALING_GOVERNOR_GLOB).all? do |governor|
131+
File.read(governor).strip == PERFORMANCE_GOVERNOR
132+
end
133+
end
134+
135+
def check_pstate(turbo:)
136+
unless turbo || turbo_disabled?
137+
puts("You forgot to disable boost:")
138+
puts(" sudo sh -c 'echo #{TURBO_DISABLED_VALUE} > #{BOOST_PATH}'")
139+
exit(-1)
140+
end
141+
142+
unless frequency_maximized?
143+
puts("You forgot to set the performance governor:")
144+
puts(" sudo cpupower frequency-set -g #{PERFORMANCE_GOVERNOR}")
145+
exit(-1)
146+
end
147+
end
148+
end
149+
150+
# Null object for unsupported CPUs
151+
class NullCPUConfig < CPUConfig
152+
private
153+
154+
def disable_frequency_scaling(turbo:)
155+
# Do nothing
156+
end
157+
158+
def check_pstate(turbo:)
159+
# Do nothing
160+
end
161+
end

run_benchmarks.rb

Lines changed: 6 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -11,101 +11,16 @@
1111
require 'etc'
1212
require 'yaml'
1313
require_relative 'misc/stats'
14+
require_relative 'lib/cpu_config'
1415
require_relative 'lib/benchmark_runner'
1516
require_relative 'lib/table_formatter'
1617
require_relative 'lib/benchmark_filter'
1718

18-
# Checked system - error or return info if the command fails
19-
def check_call(command, env: {}, raise_error: true, quiet: false)
20-
puts("+ #{command}") unless quiet
21-
22-
result = {}
23-
24-
result[:success] = system(env, command)
25-
result[:status] = $?
26-
27-
unless result[:success]
28-
puts "Command #{command.inspect} failed with exit code #{result[:status].exitstatus} in directory #{Dir.pwd}"
29-
raise RuntimeError.new if raise_error
30-
end
31-
32-
result
33-
end
34-
35-
def check_output(*command)
36-
IO.popen(*command, &:read)
37-
end
38-
3919
def have_yjit?(ruby)
40-
ruby_version = check_output("#{ruby} -v --yjit", err: File::NULL).strip
20+
ruby_version = `#{ruby} -v --yjit 2> #{File::NULL}`.strip
4121
ruby_version.downcase.include?("yjit")
4222
end
4323

44-
# Disable Turbo Boost while running benchmarks. Maximize the CPU frequency.
45-
def set_bench_config(turbo:)
46-
# sudo requires the flag '-S' in order to take input from stdin
47-
if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel
48-
unless intel_no_turbo? || turbo
49-
check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'")
50-
at_exit { check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) }
51-
end
52-
# Disabling Turbo Boost reduces the CPU frequency, so this should be run after that.
53-
check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct?
54-
elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD
55-
unless amd_no_boost? || turbo
56-
check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'")
57-
at_exit { check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) }
58-
end
59-
check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor?
60-
end
61-
end
62-
63-
def check_pstate(turbo:)
64-
if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel
65-
unless turbo || intel_no_turbo?
66-
puts("You forgot to disable turbo:")
67-
puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'")
68-
exit(-1)
69-
end
70-
71-
unless intel_perf_100pct?
72-
puts("You forgot to set the min perf percentage to 100:")
73-
puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'")
74-
exit(-1)
75-
end
76-
elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD
77-
unless turbo || amd_no_boost?
78-
puts("You forgot to disable boost:")
79-
puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'")
80-
exit(-1)
81-
end
82-
83-
unless performance_governor?
84-
puts("You forgot to set the performance governor:")
85-
puts(" sudo cpupower frequency-set -g performance")
86-
exit(-1)
87-
end
88-
end
89-
end
90-
91-
def intel_no_turbo?
92-
File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1'
93-
end
94-
95-
def intel_perf_100pct?
96-
File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100'
97-
end
98-
99-
def amd_no_boost?
100-
File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0'
101-
end
102-
103-
def performance_governor?
104-
Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor|
105-
File.read(governor).strip == 'performance'
106-
end
107-
end
108-
10924
def mean(values)
11025
Stats.new(values).mean
11126
end
@@ -132,10 +47,6 @@ def sort_benchmarks(bench_names)
13247
BenchmarkRunner.sort_benchmarks(bench_names, benchmarks_metadata)
13348
end
13449

135-
def setarch_prefix
136-
BenchmarkRunner.setarch_prefix
137-
end
138-
13950
# Run all the benchmarks and record execution times
14051
def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_path:, harness:, pre_init:, no_pinning:)
14152
bench_data = {}
@@ -193,7 +104,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
193104
# Set up the benchmarking command
194105
cmd = []
195106
if BenchmarkRunner.os == :linux
196-
cmd += setarch_prefix
107+
cmd += BenchmarkRunner.setarch_prefix
197108

198109
# Pin the process to one given core to improve caching and reduce variance on CRuby
199110
# Other Rubies need to use multiple cores, e.g., for JIT threads
@@ -230,7 +141,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
230141
end
231142

232143
# Do the benchmarking
233-
result = check_call(cmd.shelljoin, env: env, raise_error: false)
144+
result = BenchmarkRunner.check_call(cmd.shelljoin, env: env, raise_error: false)
234145

235146
if result[:success]
236147
bench_data[bench_name] = JSON.parse(File.read(result_json_path)).tap do |json|
@@ -387,18 +298,14 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
387298
end
388299
end
389300

390-
# Disable CPU frequency scaling
391-
set_bench_config(turbo: args.turbo)
392-
393-
# Check pstate status
394-
check_pstate(turbo: args.turbo)
301+
CPUConfig.configure_for_benchmarking(turbo: args.turbo)
395302

396303
# Create the output directory
397304
FileUtils.mkdir_p(args.out_path)
398305

399306
ruby_descriptions = {}
400307
args.executables.each do |name, executable|
401-
ruby_descriptions[name] = check_output([*executable, "-v"]).chomp
308+
ruby_descriptions[name] = `#{executable.shelljoin} -v`.chomp
402309
end
403310

404311
# Benchmark with and without YJIT

0 commit comments

Comments
 (0)