Benchmarking Techniques in Ruby
I am trying to do a comparative set of such calculations -
def benchmark(func, index, array)
start = Time.now
func(index, array)
start - Time.now #returns time taken to perform func
end
def func1(index, array)
#perform computations based on index and array
end
def func2(index, array)
#more computations....
end
benchmark(func1, index1, array1)
benchmark(func1, index2, array2)
Now I am wondering how I can achieve this. I tried this example but spits out
`func1': wrong number of arguments (0 for 2) (ArgumentError)
If I try -
benchmark(func1(index1, array1), index1, array1)
Spits out ...
undefined method `func' for main:Object (NoMethodError)
I saw a similar question asked about this, but this was for python. Passing functions with arguments to another function in Python? Can anyone please help? Thank.
source to share
In Ruby, methods can be called without including empty parentheses after the method name, for example:
def func1
puts "Hello!"
end
func1 # Calls func1 and prints "Hello!"
Because of this, when you write benchmark(func1, index1, array1)
, you are actually calling func1
with no arguments and passing the result in benchmark
without passing func1
to the reference function as you would expect. To pass func1
as an object, you can get the wrapper object for the function using a method method
like:
def func1
puts "Hello!"
end
m = method(:func1) # Returns a Method object for func1
m.call(param1, param2)
In most cases, this is not what you really want to do. Ruby supports a construct called blocks, which is much better suited for this purpose. You may already be familiar with blocks from Tether each
, which Ruby uses to loop through arrays. This is how it would look like using blocks for your use case:
def benchmark
start = Time.now
yield
Time.now - start # Returns time taken to perform func
end
# Or alternately:
# def benchmark(&block)
# start = Time.now
# block.call
# Time.now - start # Returns time taken to perform func
# end
def func1(index, array)
# Perform computations based on index and array
end
def func2(index, array)
# More computations....
end
benchmark { func1(index1, array1) }
benchmark { func1(index1, array2) }
In fact, Ruby has a standard benchmarking library called Benchmark that uses blocks and probably does exactly what you want it to do already.
Using:
require 'benchmark' n = 5000000 Benchmark.bm do |x| x.report { for i in 1..n; a = "1"; end } x.report { n.times do ; a = "1"; end } x.report { 1.upto(n) do ; a = "1"; end } end
Result:
user system total real 1.010000 0.000000 1.010000 ( 1.014479) 1.000000 0.000000 1.000000 ( 0.998261) 0.980000 0.000000 0.980000 ( 0.981335)
source to share
This is how I do it. First, I create a module containing all the methods for testing:
module Methods
def bob(array)
...
end
def gretta(array
...
end
def wilma(array)
...
end
end
then I include the module and put the methods into an array:
include Methods
@methods = Methods.public_instance_methods(false)
#=> [:bob, :gretta, :wilma]
This allows me to execute send(meth, *args)
for each method meth
in the @methods
.
Here's an example of this approach. You will see that I also have some code to check that all methods return the same result and format the output.
The basic procedure might look something like this:
test_sizes.each do |n|
puts "\nn = #{n}"
arr = test_array(n)
Benchmark.bm(@indent) do |bm|
@methods.each do |m|
bm.report m.to_s do
send(m, arr)
end
end
end
end
I use a module so that methods that can be compared, added, removed, or renamed without having to touch any code outside the module.
source to share
It looks like you are trying to use Ruby methods as functions. This is unusual, but entirely possible.
def benchmark(func, index, array)
start = Time.now
func.call(index, array) # <= (C)
start - Time.now
end
def func1(index, array)
#perform computations based on index and array
end
def func2(index, array)
#more computations....
end
benchmark(method(:func1), index1, array1) # <= (A)
benchmark(method(:func1), index2, array2) # <= (B)
Your code changes look like this:
A, B) Create an object Method
from your previously defined methods. An object Method
is similar to an object Proc
in that it has a method call
that allows you to call it later. In your code, when you just use func1
, and not method(:func1)
, what happens is that you immediately call the method and pass its result to benchmark
, instead of passing that function to benchmark
for later call.
C) Use method call
. Ruby doesn't allow you to arbitrarily call variables as functions using parentheses like some other languages ββdo, but if it's an object type with a method call
like Method
or Proc
, you can use call
to call the function when you're ready, rather than immediately before try to pass it to another method, as in the source code.
source to share
class SimpleBenchmarker
def self.go(how_many=1, &block)
$sorted_time = Array.new
puts "\n--------------Benchmarking started----------------"
start_time = Time.now
puts "Start Time:\t#{start_time}\n\n"
how_many.times do |a|
print "."
block.call
end
print "\n\n"
end_time = Time.now
puts "End Time:\t#{end_time}\n"
puts "-------------Benchmarking finished----------------\n\n"
result_time = end_time - start_time
puts "Total time:\t\t#{result_time.round(3)} seconds\n\n"
puts "The run times for the iterations from shortest to longest was: #{$sorted_time.sort.join("s, ")}s\n\n"
end
end
print "How many times? "
t = gets.to_i
SimpleBenchmarker.go t do
time = rand(0.1..1.0).round(3)
$sorted_time.push(time)
sleep time
end
source to share
Please check out my new gem that can profile your ruby ββmethod (instance or class) - https://github.com/igorkasyanchuk/benchmark_methods .
No more code:
t = Time.now user.calculate_report puts Time.now - t
Now you can do:
benchmark :calculate_report # in class
And just call your method
user.calculate_report
source to share