Skip to content

Commit 3dfeccc

Browse files
committed
Add benchmarks
1 parent 0d3225a commit 3dfeccc

5 files changed

Lines changed: 426 additions & 4 deletions

File tree

Gemfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ gem "minitest", "~> 5.16"
1414

1515
gem "rubocop", "~> 1.21"
1616
gem "rubocop-shopify", require: false
17+
18+
group :benchmark do
19+
gem "benchmark-ips"
20+
gem "sorbet-runtime"
21+
end

Gemfile.lock

Lines changed: 8 additions & 4 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)
@@ -112,18 +113,21 @@ PLATFORMS
112113
arm64-darwin-24
113114

114115
DEPENDENCIES
116+
benchmark-ips
115117
irb
116118
minitest (~> 5.16)
117119
rake (~> 13.0)
118120
rubocop (~> 1.21)
119121
rubocop-shopify
120122
sorbet
123+
sorbet-runtime
121124
tapioca (~> 0.17)
122125
type_toolkit!
123126

124127
CHECKSUMS
125128
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
126129
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
130+
benchmark-ips (2.14.0) sha256=b72bc8a65d525d5906f8cd94270dccf73452ee3257a32b89fbd6684d3e8a9b1d
127131
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
128132
erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
129133
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
@@ -145,11 +149,11 @@ CHECKSUMS
145149
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
146150
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
147151
rbi (0.3.9) sha256=d6a187bd0b376e999d3d82a5e5798a61178be98b894b7b35741c14162c9ea015
148-
rbs (4.0.0.dev.5)
152+
rbs (4.0.0.dev.5) sha256=273938e5a9c7f06b041be583588f2aa1976149bda2ce213e33e129c51534e174
149153
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
150154
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
151155
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
152-
require-hooks (0.2.2)
156+
require-hooks (0.2.2) sha256=effa81b61a669ca00cb1e8910411e0d6a8bb16b49a53d9e69b5e6372d6c90f31
153157
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
154158
rubocop (1.84.2) sha256=5692cea54168f3dc8cb79a6fe95c5424b7ea893c707ad7a4307b0585e88dbf5f
155159
rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd
@@ -159,9 +163,9 @@ CHECKSUMS
159163
sorbet-runtime (0.6.12945) sha256=3ddd992650437557fb87deb13c94d7250aeeab326812e7a863dfd4955f0758f3
160164
sorbet-static (0.6.12945-universal-darwin) sha256=766038d71e0ab7f8a60eb298b742d4e5fb9bd364034c651443f825fb4d61f11e
161165
sorbet-static-and-runtime (0.6.12945) sha256=6a0ae4ac59ddc874e9438ff6e0f23b4eb78569c6d412ced4e043f3a6db6b44d0
162-
spoom (1.7.11)
166+
spoom (1.7.11) sha256=4e27384af6d3fde5aadc0287c51e6f76c0802259cbb3b6a67603bf718352f4cf
163167
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
164-
tapioca (0.17.10)
168+
tapioca (0.17.10) sha256=880a682ca8314f798dd09e9f104134fbf1a713c13be51f7dd4741dd434e6471b
165169
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
166170
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
167171
type_toolkit (0.0.2)
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)