Last active
May 25, 2023 12:00
-
-
Save jaynetics/3d8d0e5cfcbec3faf6412e0bd2b1adc4 to your computer and use it in GitHub Desktop.
Performance overhead of Ruby TracePoint API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###### | |
# Simplest case - one method that does nothing, with and without TP | |
###### | |
require 'benchmark/ips' | |
def noop; end | |
Benchmark.ips { |x| x.report('basic', 'noop') }; 1 | |
trace = TracePoint.new(:call) { |tp| } | |
trace.enable do | |
Benchmark.ips { |x| x.report('trace', 'noop') }; 1 | |
end | |
# RESULTS | |
# => basic 31.860M (± 0.5%) i/s - 161.220M in 5.060445s | |
# => trace 12.411M (± 0.6%) i/s - 62.084M in 5.002639s | |
###### | |
# Comparison of various ways to intercept calls, | |
# with a more realistic method chain | |
###### | |
# Playing with the numbers affects results. | |
# More method calls = worse relative tracepoint performance, | |
# but only up to a value of 10 or so. | |
METHOD_CALLS = 10 | |
# More allocations or work per method = better relative tracepoint performance | |
ALLOCATIONS_PER_METHOD = 5 | |
module M | |
(1..METHOD_CALLS).each do |n| | |
eval <<~RUBY | |
def method#{n} | |
Array.new(ALLOCATIONS_PER_METHOD) { Object.new } | |
#{"method#{n + 1}" if n < METHOD_CALLS} | |
end | |
RUBY | |
end | |
end | |
object = Object.new.extend(M) | |
object_with_prepend = Object.new.extend(M) | |
object_with_prepend.singleton_class.prepend Module.new { | |
def method1 | |
$callers << caller_locations(1, 1) | |
super | |
end | |
} | |
object_with_alias = Object.new.extend(M) | |
object_with_alias.instance_eval do | |
alias original_method1 method1 | |
def method1 | |
$callers << caller_locations(1, 1) | |
original_method1 | |
end | |
end | |
object_with_rebind = Object.new.extend(M) | |
original_method1 = M.instance_method(:method1) | |
object_with_rebind.define_singleton_method(:method1) do | |
$callers << caller_locations(1, 1) | |
original_method1.bind_call(self) | |
end | |
trace = TracePoint.new(:call) { |tp| $callers << tp } | |
Benchmark.ips do |x| | |
x.report('default') do | |
object.method1 | |
end | |
x.report('prepended') do | |
$callers = [] | |
object_with_prepend.method1 | |
end | |
x.report('aliased') do | |
$callers = [] | |
object_with_alias.method1 | |
end | |
x.report('rebound') do | |
$callers = [] | |
object_with_rebind.method1 | |
end | |
x.report('traced') do | |
$callers = [] | |
trace.enable | |
object.method1 | |
trace.disable | |
end | |
x.compare! | |
end; 1 | |
# RESULTS | |
# default: 214501.5 i/s | |
# aliased: 200091.9 i/s - 1.07x slower | |
# prepended: 200087.1 i/s - 1.07x slower | |
# rebound: 196798.0 i/s - 1.09x slower | |
# traced: 174653.6 i/s - 1.23x slower |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment