Skip to content

Commit 92bd1c6

Browse files
Port the Ellis-Kovac-Boehm GC benchmark
Run with: ./run_benchmarks.rb --category=gc
1 parent 70aba1f commit 92bd1c6

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

benchmarks.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ binarytrees:
7474
blurhash:
7575
desc: blurhash (blurred preview image) calculation
7676
ractor: true
77+
gcbench:
78+
desc: Ellis-Kovac-Boehm GCBench builds binary trees of various depths to exercise GC marking, sweeping, and write barriers.
79+
category: gc
80+
single_file: true
81+
default_harness: harness-gc
7782
erubi:
7883
desc: erubi compiles a simple Erb template into a method with erubi, then times evaluating that method.
7984
ractor: true

benchmarks/gcbench.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Ellis-Kovac-Boehm GCBench
2+
#
3+
# Adapted from the benchmark by John Ellis and Pete Kovac (Post Communications),
4+
# modified by Hans Boehm (Silicon Graphics), translated to Ruby by Noel Padavan
5+
# and Chris Seaton. Adapted for yjit-bench by Matt Valentine-House.
6+
#
7+
# Builds balanced binary trees of various depths to generate objects with a range
8+
# of lifetimes. Two long-lived structures (a tree and a float array) are kept
9+
# alive throughout to model applications that maintain persistent heap data.
10+
#
11+
# Tree construction uses both top-down (populate — creates old-to-young pointers,
12+
# exercises write barriers) and bottom-up (make_tree — young-to-young only).
13+
14+
require_relative '../harness/loader'
15+
16+
class GCBench
17+
class Node
18+
attr_accessor :left, :right, :i, :j
19+
20+
def initialize(left = nil, right = nil)
21+
@left = left
22+
@right = right
23+
@i = 0
24+
@j = 0
25+
end
26+
end
27+
28+
STRETCH_TREE_DEPTH = 18
29+
LONG_LIVED_TREE_DEPTH = 16
30+
ARRAY_SIZE = 500_000
31+
MIN_TREE_DEPTH = 4
32+
MAX_TREE_DEPTH = 16
33+
34+
def self.tree_size(depth)
35+
(1 << (depth + 1)) - 1
36+
end
37+
38+
def self.num_iters(depth)
39+
2 * tree_size(STRETCH_TREE_DEPTH) / tree_size(depth)
40+
end
41+
42+
# Top-down: assigns children to an existing (older) node — old-to-young pointers.
43+
def self.populate(depth, node)
44+
if depth > 0
45+
depth -= 1
46+
node.left = Node.new
47+
node.right = Node.new
48+
populate(depth, node.left)
49+
populate(depth, node.right)
50+
end
51+
end
52+
53+
# Bottom-up: children allocated before parent — young-to-young pointers only.
54+
def self.make_tree(depth)
55+
if depth <= 0
56+
Node.new
57+
else
58+
Node.new(make_tree(depth - 1), make_tree(depth - 1))
59+
end
60+
end
61+
62+
def self.time_construction(depth)
63+
n = num_iters(depth)
64+
65+
n.times do
66+
node = Node.new
67+
populate(depth, node)
68+
end
69+
70+
n.times do
71+
make_tree(depth)
72+
end
73+
end
74+
end
75+
76+
# Stretch the heap before measurement
77+
GCBench.make_tree(GCBench::STRETCH_TREE_DEPTH)
78+
79+
# Long-lived objects that persist across all iterations
80+
long_lived_tree = GCBench::Node.new
81+
GCBench.populate(GCBench::LONG_LIVED_TREE_DEPTH, long_lived_tree)
82+
83+
long_lived_array = Array.new(GCBench::ARRAY_SIZE)
84+
(GCBench::ARRAY_SIZE / 2).times { |i| long_lived_array[i + 1] = 1.0 / (i + 1) }
85+
86+
run_benchmark(10) do
87+
GCBench::MIN_TREE_DEPTH.step(GCBench::MAX_TREE_DEPTH, 2) do |depth|
88+
GCBench.time_construction(depth)
89+
end
90+
end

0 commit comments

Comments
 (0)