Skip to content

Instantly share code, notes, and snippets.

@simpl1g
Created March 28, 2024 18:38
Show Gist options
  • Save simpl1g/e34c073a8e33128d1f309873d9164967 to your computer and use it in GitHub Desktop.
Save simpl1g/e34c073a8e33128d1f309873d9164967 to your computer and use it in GitHub Desktop.
Profile comparison
# frozen_string_literal: true
require 'benchmark/ips'
require 'redis'
$redis = Redis.new
class MethodProfiler
def self.patch_using_alias_method(klass, methods, name)
patch_source_line = __LINE__ + 3
patches = methods.map do |method_name|
<<~RUBY
unless defined?(#{method_name}__mp_unpatched)
alias_method :#{method_name}__mp_unpatched, :#{method_name}
def #{method_name}(*args, &blk)
unless prof = Thread.current[:_method_profiler]
return #{method_name}__mp_unpatched(*args, &blk)
end
begin
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
#{method_name}__mp_unpatched(*args, &blk)
ensure
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
data[:calls] += 1
end
end
end
RUBY
end.join("\n")
klass.class_eval patches, __FILE__, patch_source_line
end
def self.patch_using_alias_method_triple_dot(klass, methods, name)
patches = methods.map do |method_name|
<<~RUBY
unless defined?(#{method_name}__mp_unpatched)
alias_method :#{method_name}__mp_unpatched, :#{method_name}
def #{method_name}(...)
unless prof = Thread.current[:_method_profiler]
return #{method_name}__mp_unpatched(...)
end
begin
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
#{method_name}__mp_unpatched(...)
ensure
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
data[:calls] += 1
end
end
end
RUBY
end.join("\n")
klass.class_eval patches
end
def self.define_methods_on_module(klass, methods, name)
patch_source_line = __LINE__ + 3
patches = methods.map do |method_name|
<<~RUBY
def #{method_name}(*args, &blk)
unless prof = Thread.current[:_method_profiler]
return super
end
begin
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
super
ensure
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
data[:calls] += 1
end
end
RUBY
end.join("\n")
klass.module_eval patches, __FILE__, patch_source_line
end
def self.patch_using_prepend(klass, methods, name)
prepend_instrument = Module.new
define_methods_on_module(prepend_instrument, methods, name)
klass.prepend(prepend_instrument)
end
def self.define_methods_on_module_triple_dot(klass, methods, name)
patch_source_line = __LINE__ + 3
patches = methods.map do |method_name|
<<~RUBY
def #{method_name}(...)
unless prof = Thread.current[:_method_profiler]
return super
end
begin
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
super
ensure
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
data[:calls] += 1
end
end
RUBY
end.join("\n")
klass.module_eval patches, __FILE__, patch_source_line
end
def self.patch_using_prepend_triple_dot(klass, methods, name)
return if klass.constants.include?(:MethodProfilerPrepend)
prepend_instrument = Module.new
define_methods_on_module_triple_dot(prepend_instrument, methods, name)
klass.const_set(:MethodProfilerPrepend, prepend_instrument)
klass.prepend(prepend_instrument)
end
def self.start
Thread.current[:_method_profiler] = {
__start: Process.clock_gettime(Process::CLOCK_MONOTONIC),
}
end
def self.stop
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
return unless (data = Thread.current[:_method_profiler])
Thread.current[:_method_profiler] = nil
start = data.delete(:__start)
data[:total_duration] = finish - start
data
end
end
class Test
def work
# $redis.get('asd')
# 1 + 1
end
def method_unpatched(sql, cache: false, array: false, **settings)
work
end
def method(sql, cache: false, array: false, **settings)
work
end
def method_triple_dot(sql, cache: false, array: false, **settings)
work
end
def method_prepend(sql, cache: false, array: false, **settings)
work
end
def method_prepend_triple_dot(sql, cache: false, array: false, **settings)
work
end
end
MethodProfiler.patch_using_alias_method(Test, [:method], :patch_using_alias_method)
MethodProfiler.patch_using_alias_method_triple_dot(Test, [:method_triple_dot], :patch_using_alias_method_triple_dot)
MethodProfiler.patch_using_prepend(Test, [:method_prepend], :patch_using_prepend)
MethodProfiler.patch_using_prepend_triple_dot(Test, [:method_prepend_triple_dot], :patch_using_prepend_triple_dot)
MethodProfiler.start
t = Test.new
sql = 'select * from table'
Benchmark.ips do |b|
# b.report 'method_unpatched' do |times|
# i = 0
# while i < times
# t.method_unpatched(sql)
# i += 1
# end
# end
b.report 'method' do |times|
i = 0
while i < times
t.method(sql)
i += 1
end
end
b.report 'method_triple_dot' do |times|
i = 0
while i < times
t.method_triple_dot(sql)
i += 1
end
end
b.report 'method_prepend' do |times|
i = 0
while i < times
t.method_prepend(sql)
i += 1
end
end
b.report 'method_prepend_triple_dot' do |times|
i = 0
while i < times
t.method_prepend_triple_dot(sql)
i += 1
end
end
b.compare!
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment