Created
March 28, 2024 18:38
-
-
Save simpl1g/e34c073a8e33128d1f309873d9164967 to your computer and use it in GitHub Desktop.
Profile comparison
This file contains 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
# 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