Skip to content

Commit 467fc19

Browse files
committed
Add benchmarks/ractor directory with 2 JSON benchmarks
Add JSON benchmarks: parse_float parse_string Add to ractor harness, make it possible to use gemfiles in different directories and make it easy to do so. This way we don't have to create directories for each benchmark. Improve run_benchmarks.rb, allow it to take a regular expression for --name_filters option Ex: $ WARMUP_ITRS=10 ruby run_benchmarks.rb --category=ractor-only --name_filters=/json/ Allow it to take --skip-yjit option to only run with interpreter.
1 parent d501106 commit 467fc19

File tree

7 files changed

+149
-8
lines changed

7 files changed

+149
-8
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gem "json", "2.13.2"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
GEM
2+
specs:
3+
json (2.13.2)
4+
5+
PLATFORMS
6+
arm64-darwin-24
7+
ruby
8+
9+
DEPENDENCIES
10+
json (= 2.13.2)
11+
12+
BUNDLED WITH
13+
2.8.0.dev
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require_relative "../../harness/loader"
2+
3+
use_ractor_gemfile("json_parse")
4+
puts "JSON #{JSON::VERSION}"
5+
6+
ELEMENTS = 100_000
7+
8+
list = ELEMENTS.times.map do
9+
{
10+
rand => rand,
11+
rand => rand,
12+
rand => rand,
13+
rand => rand,
14+
rand => rand,
15+
rand => rand,
16+
rand => rand,
17+
rand => rand,
18+
rand => rand,
19+
rand => rand,
20+
}.to_json
21+
end
22+
Ractor.make_shareable(list)
23+
24+
# Work is divided between ractors
25+
run_benchmark(5, ractor_args: [list]) do |num_rs, list|
26+
# num_rs: 1,list: 100_000
27+
# num_rs: 2 list: 50_000
28+
# num_rs: 4 list: 25_000
29+
if num_rs.zero?
30+
num = list.size
31+
else
32+
num = list.size / num_rs
33+
end
34+
list.each_with_index do |json, idx|
35+
break if idx >= num
36+
JSON.parse(json)
37+
end
38+
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
require_relative "../../harness/loader"
2+
3+
use_ractor_gemfile("json_parse")
4+
puts "JSON #{JSON::VERSION}"
5+
6+
ELEMENTS = 300_000
7+
list = ELEMENTS.times.map do |i|
8+
{
9+
"string #{i}" => "value #{i}",
10+
"string #{i}" => "value #{i}",
11+
"string #{i}" => "value #{i}",
12+
"string #{i}" => "value #{i}",
13+
"string #{i}" => "value #{i}",
14+
"string #{i}" => "value #{i}",
15+
"string #{i}" => "value #{i}",
16+
"string #{i}" => "value #{i}",
17+
"string #{i}" => "value #{i}",
18+
"string #{i}" => "value #{i}",
19+
"string #{i}" => "value #{i}",
20+
"string #{i}" => "value #{i}",
21+
"string #{i}" => "value #{i}",
22+
"string #{i}" => "value #{i}",
23+
"string #{i}" => "value #{i}",
24+
"string #{i}" => "value #{i}",
25+
"string #{i}" => "value #{i}",
26+
"string #{i}" => "value #{i}",
27+
"string #{i}" => "value #{i}",
28+
"string #{i}" => "value #{i}",
29+
}.to_json
30+
end
31+
Ractor.make_shareable(list)
32+
33+
# Work is divided between ractors
34+
run_benchmark(5, ractor_args: [list]) do |num_rs, list|
35+
# num_rs: 1,list: 100_000
36+
# num_rs: 2 list: 50_000
37+
# num_rs: 4 list: 25_000
38+
if num_rs.zero?
39+
num = list.size
40+
else
41+
num = list.size / num_rs
42+
end
43+
list.each_with_index do |json, idx|
44+
break if idx >= num
45+
JSON.parse(json)
46+
end
47+
end

harness-ractor/harness.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ def join
2727
end
2828
end
2929

30+
def use_ractor_gemfile(filename)
31+
filename = File.expand_path("Gemfile_#{filename}.rb", "benchmarks/ractor/gemfiles")
32+
raise "Gemfile #{filename} doesn't exist" unless File.exist?(filename)
33+
use_inline_gemfile do
34+
instance_eval File.read(filename), filename, 1
35+
end
36+
end
37+
3038
MAX_ITERS = Integer(ENV.fetch("MAX_BENCH_ITRS", 5))
3139

3240
def run_benchmark(num_itrs_hint, ractor_args: [], &block)
@@ -98,4 +106,4 @@ def ractor_deep_dup(args)
98106
end
99107
end
100108

101-
Ractor.make_shareable(self)
109+
Ractor.make_shareable(self) # until we get Ractor.shareable_proc

harness/harness-common.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,20 @@ def use_gemfile(extra_setup_cmd: nil)
4646
# Need to be in the appropriate directory for this...
4747
require "bundler"
4848
# Use Bundler.setup instead of require 'bundler/setup' to avoid bundler's autoswitch restarting the
49-
# process and messing with LOAD_PATH.
49+
# process and messing with LOAD_PATH. Autoswitching occurs when the BUNDLED_WITH in the Gemfile.lock
50+
# is a different version than the loaded version of bundler.
5051
Bundler.setup
5152
end
5253

54+
def use_inline_gemfile(&block)
55+
raise "Block must be given" unless block
56+
require "bundler/inline"
57+
gemfile(install=true) do
58+
source "https://rubygems.org"
59+
instance_eval(&block)
60+
end
61+
end
62+
5363
# This returns its best estimate of the Resident Set Size in bytes.
5464
# That's roughly the amount of memory the process takes, including shareable resources.
5565
# RSS reference: https://stackoverflow.com/questions/7880784/what-is-rss-and-vsz-in-linux-memory-management

run_benchmarks.rb

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,28 @@ def benchmark_categories(name)
183183

184184
# Check if the name matches any of the names in a list of filters
185185
def match_filter(entry, categories:, name_filters:)
186+
name_filters = process_name_filters(name_filters)
186187
name = entry.sub(/\.rb\z/, '')
187188
(categories.empty? || benchmark_categories(name).any? { |cat| categories.include?(cat) }) &&
188-
(name_filters.empty? || name_filters.include?(name))
189+
(name_filters.empty? || name_filters.any? { |filter| filter === name })
190+
end
191+
192+
# process "/my_benchmark/i" into /my_benchmark/i
193+
def process_name_filters(name_filters)
194+
name_filters.map do |name_filter|
195+
if name_filter[0] == "/"
196+
regexp_str = name_filter[1..-1].reverse.sub(/\A(\w*)\//, "")
197+
regexp_opts = $1.to_s
198+
regexp_str.reverse!
199+
r = /#{regexp_str}/
200+
if !regexp_opts.empty?
201+
r = Regexp.compile(r.to_s, regexp_opts)
202+
end
203+
r
204+
else
205+
name_filter
206+
end
207+
end
189208
end
190209

191210
# Resolve the pre_init file path into a form that can be required
@@ -332,6 +351,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
332351
graph: false,
333352
no_pinning: false,
334353
turbo: false,
354+
skip_yjit: false,
335355
})
336356

337357
OptionParser.new do |opts|
@@ -372,23 +392,27 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
372392

373393
opts.on("--category=headline,other,micro,ractor", "when given, only benchmarks with specified categories will run") do |v|
374394
args.categories += v.split(",")
375-
if args.categories == ['ractor']
376-
args.harness = 'harness-ractor'
395+
if args.categories == ["ractor"]
396+
args.harness = "harness-ractor"
377397
end
378398
end
379399

380-
opts.on("--headline", "when given, headline benchmarks will be run") do |v|
400+
opts.on("--headline", "when given, headline benchmarks will be run") do
381401
args.categories += ["headline"]
382402
end
383403

384-
opts.on("--ractor-only", "ractor-only benchmarks (benchmarks/ractor/*.rb) will be run") do |v|
404+
opts.on("--ractor-only", "ractor-only benchmarks (benchmarks/ractor/*.rb) will be run") do
385405
args.categories = ["ractor-only"]
386406
end
387407

388408
opts.on("--name_filters=x,y,z", Array, "when given, only benchmarks with names that contain one of these strings will run") do |list|
389409
args.name_filters = list
390410
end
391411

412+
opts.on("--skip-yjit", "Don't run with yjit after interpreter") do
413+
args.skip_yjit = true
414+
end
415+
392416
opts.on("--harness=HARNESS_DIR", "which harness to use") do |v|
393417
v = "harness-#{v}" unless v.start_with?('harness')
394418
args.harness = v
@@ -446,7 +470,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
446470

447471
# If -e is not specified, benchmark the current Ruby. Compare it with YJIT if available.
448472
if args.executables.empty?
449-
if have_yjit?(RbConfig.ruby)
473+
if have_yjit?(RbConfig.ruby) && !args.skip_yjit
450474
args.executables["interp"] = [RbConfig.ruby]
451475
args.executables["yjit"] = [RbConfig.ruby, "--yjit", *args.yjit_opts.shellsplit]
452476
else

0 commit comments

Comments
 (0)