Skip to content

Instantly share code, notes, and snippets.

@jaynetics
Last active May 25, 2023 12:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaynetics/3d8d0e5cfcbec3faf6412e0bd2b1adc4 to your computer and use it in GitHub Desktop.
Save jaynetics/3d8d0e5cfcbec3faf6412e0bd2b1adc4 to your computer and use it in GitHub Desktop.
Performance overhead of Ruby TracePoint API
######
# 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