Skip to content

Commit 2a1f136

Browse files
committed
Add benchmarks
1 parent 2b19dd7 commit 2a1f136

7 files changed

Lines changed: 1410 additions & 0 deletions

File tree

Gemfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ gem "rubocop", "~> 1.21"
1616
gem "rubocop-shopify", require: false
1717
gem "rubocop-minitest", require: false
1818
gem "rubocop-rake", require: false
19+
20+
group :benchmark do
21+
gem "benchmark-ips"
22+
gem "sorbet-runtime"
23+
end

Gemfile.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ GEM
88
specs:
99
ast (2.4.3)
1010
benchmark (0.5.0)
11+
benchmark-ips (2.14.0)
1112
date (3.5.1)
1213
erb (6.0.1)
1314
erubi (1.13.1)
@@ -121,6 +122,7 @@ PLATFORMS
121122
x86_64-linux
122123

123124
DEPENDENCIES
125+
benchmark-ips
124126
irb
125127
minitest (~> 5.16)
126128
rake (~> 13.0)
@@ -129,12 +131,14 @@ DEPENDENCIES
129131
rubocop-rake
130132
rubocop-shopify
131133
sorbet
134+
sorbet-runtime
132135
tapioca (>= 0.17)
133136
type_toolkit!
134137

135138
CHECKSUMS
136139
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
137140
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
141+
benchmark-ips (2.14.0) sha256=b72bc8a65d525d5906f8cd94270dccf73452ee3257a32b89fbd6684d3e8a9b1d
138142
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
139143
erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
140144
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9

benchmark/.rubocop.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
inherit_from: ../.rubocop.yml
2+
3+
Naming/ClassAndModuleCamelCase:
4+
Enabled: false # Sometimes underscores are useful, m'kay?
5+
6+
Style/ClassMethodsDefinitions:
7+
Enabled: false # We need to be able to compare `class << self` and `def self.`
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# typed: ignore
2+
# frozen_string_literal: true
3+
4+
# Benchmark the performance overhead of calling:
5+
# - A concrete implementation of an abstract method
6+
# - An inherited concrete implementation of an abstract method
7+
# - The error case of calling an unimplemented abstract method
8+
9+
############################################# Results #############################################
10+
#
11+
# ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin23]
12+
#
13+
# ## Interpretter
14+
#
15+
# | Call to... | Regular impl | Inherited impl | Missing impl |
16+
# |-------------------|--------------------:|------------------------:|--------------------------:|
17+
# | sorbet-runtime | (same-ish) 23.02 ns | (2.70x slower) 57.30 ns | (1.13x slower) 472.86 ns |
18+
# | manual delegation | (same-ish) 22.18 ns | (2.07x slower) 44.90 ns | *415.36 ns* |
19+
# | type_toolkit | (same-ish) 22.56 ns | *22.03 ns* | (2.11x slower) 890.38 ns |
20+
#
21+
# ## YJIT#
22+
# | Call to... | Regular impl | Inherited impl | Missing impl |
23+
# |-------------------|--------------------:|-------------------------:|--------------------------:|
24+
# | sorbet-runtime | (same-ish) 1.63 ns | (21.41x slower) 34.91 ns | (1.10x slower) 447.59 ns |
25+
# | manual delegation | (same-ish) 1.63 ns | (7.15x slower) 11.66 ns | *405.84 ns* |
26+
# | type_toolkit | (same-ish) 1.67 ns | *1.63 ns* | (1.91x slower) 774.91 ns |
27+
#
28+
####################################################################################################
29+
30+
require "bundler"
31+
Bundler.require(:default, :benchmark)
32+
33+
require "type_toolkit"
34+
require "type_toolkit/ext"
35+
36+
module TypeKitDemo
37+
# Provides the concrete implementation of `m`
38+
class Parent
39+
def m1 = "Parent#m1"
40+
end
41+
42+
module I
43+
interface!
44+
45+
abstract def m1; end
46+
abstract def m2; end
47+
abstract def not_implemented; end
48+
end
49+
50+
# Inherits the concrete implementation of `m` from DemoParentClass.
51+
class Child < Parent
52+
include I
53+
54+
def m2 = "Child#m2"
55+
end
56+
end
57+
58+
module SorbetRuntimeDemo
59+
# Provides the concrete implementation of `m`
60+
class Parent
61+
def m1 = "Parent#m1"
62+
end
63+
64+
module I
65+
extend T::Sig
66+
extend T::Helpers
67+
68+
interface!
69+
70+
sig { abstract.returns(String) }
71+
def m1; end
72+
73+
sig { abstract.returns(String) }
74+
def m2; end
75+
76+
sig { abstract.returns(String) }
77+
def not_implemented; end
78+
end
79+
80+
# Inherits the concrete implementation of `m` from DemoParentClass.
81+
class Child < Parent
82+
include I
83+
84+
def m2 = "Child#m2"
85+
end
86+
end
87+
88+
module ManualDelegationDemo
89+
class Parent
90+
def m1 = "Parent#m1"
91+
end
92+
93+
module I
94+
def m1 = defined?(super) ? super : raise
95+
def m2 = defined?(super) ? super : raise
96+
def not_implemented = defined?(super) ? super : raise
97+
end
98+
99+
# Inherits the concrete implementation of `m` from DemoParentClass.
100+
class Child < Parent
101+
include I
102+
103+
def m2 = "Child#m2"
104+
end
105+
end
106+
107+
type_toolkit_object = TypeKitDemo::Child.new
108+
manual_delegation_object = ManualDelegationDemo::Child.new
109+
sorbet_runtime_object = SorbetRuntimeDemo::Child.new
110+
111+
[:interpretter, :yjit].each do |mode|
112+
if mode == :yjit
113+
puts <<~MSG
114+
115+
116+
================================================================================
117+
Enabling YJIT...
118+
================================================================================
119+
120+
121+
MSG
122+
RubyVM::YJIT.enable
123+
end
124+
125+
warmup = 5
126+
time = 10
127+
128+
width = ["type_toolkit", "sorbet-runtime", "manual delegation"].max_by(&:length).length
129+
130+
puts "Benchmark the performance of calling the concrete implementation directly..."
131+
Benchmark.ips do |x|
132+
x.config(warmup:, time:)
133+
134+
x.report("type_toolkit".rjust(width)) do |times|
135+
i = 0
136+
while (i += 1) < times
137+
type_toolkit_object.m2
138+
end
139+
end
140+
141+
x.report("sorbet-runtime".rjust(width)) do |times|
142+
i = 0
143+
while (i += 1) < times
144+
sorbet_runtime_object.m2
145+
end
146+
end
147+
148+
x.report("manual delegation".rjust(width)) do |times|
149+
i = 0
150+
while (i += 1) < times
151+
manual_delegation_object.m2
152+
end
153+
end
154+
155+
x.compare!
156+
end
157+
158+
puts "\n\nBenchmark the performance of calling the inherited concrete implementation..."
159+
Benchmark.ips do |x|
160+
x.config(warmup:, time:)
161+
162+
x.report("type_toolkit".rjust(width)) do |times|
163+
i = 0
164+
while (i += 1) < times
165+
type_toolkit_object.m1
166+
end
167+
end
168+
169+
x.report("sorbet-runtime".rjust(width)) do |times|
170+
i = 0
171+
while (i += 1) < times
172+
sorbet_runtime_object.m1
173+
end
174+
end
175+
176+
x.report("manual delegation".rjust(width)) do |times|
177+
i = 0
178+
while (i += 1) < times
179+
manual_delegation_object.m1
180+
end
181+
end
182+
183+
x.compare!
184+
end
185+
186+
puts "\n\nTest the performance of calling an unimplemented abstract method..."
187+
Benchmark.ips do |x|
188+
x.config(warmup:, time:)
189+
190+
x.report("type_toolkit".rjust(width)) do |times|
191+
i = 0
192+
while (i += 1) < times
193+
begin
194+
type_toolkit_object.not_implemented
195+
rescue AbstractMethodNotImplementedError # rubocop:disable Lint/SuppressedException
196+
end
197+
end
198+
end
199+
200+
x.report("sorbet-runtime".rjust(width)) do |times|
201+
i = 0
202+
while (i += 1) < times
203+
begin
204+
sorbet_runtime_object.not_implemented
205+
rescue NotImplementedError # rubocop:disable Lint/SuppressedException
206+
end
207+
end
208+
end
209+
210+
x.report("manual delegation".rjust(width)) do |times|
211+
i = 0
212+
while (i += 1) < times
213+
begin
214+
manual_delegation_object.not_implemented
215+
rescue StandardError # rubocop:disable Lint/SuppressedException
216+
end
217+
end
218+
end
219+
220+
x.compare!
221+
end
222+
end

0 commit comments

Comments
 (0)