|
| 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