Benchmarking with Ruby by makaroni4
A great technique to improve your code is using benchmarking – comparison between different solutions leads to faster and better code.
Examples under the cut.
#ruby already has Benchmark module which can be used for measuring time of running of your code. For example lets compare string concatenation methods += and <<:
require 'benchmark'
Benchmark.bm do |r|
N = 100000
r.report("+= ") do
s = ""
N.times { s += "1" }
end
r.report("<< ") do
s = ""
N.times { s << "1" }
end
end
# => user system total real
# => += 1.060000 0.340000 1.400000 ( 1.405588)
# => << 0.030000 0.000000 0.030000 ( 0.025614)
This case is great and you can see the huge difference between using += and << methods. But benchmark timing approach is not scientific – time depends on many params and the results will be different for each run. Good approach is to run your benchmark 'experiment' several times and than calculate average.
Take a look at #gem benchmark_suite, it is a great for benchmarking. Basically it provides extension for standart benchmark. So lets look at previous example implemented with benchmark_suite:
require 'benchmark'
require 'benchmark/ips'
Benchmark.ips do |r|
N = 100000
r.report("+= ") do
s = ""
N.times { s += "." }
end
r.report("<< ") do
s = ""
N.times { s << "." }
end
end
# => Calculating -------------------------------------
# => += 1 i/100ms
# => << 3 i/100ms
# =>-------------------------------------------------
# => += 0.2 (±0.0%) i/s - 2 in 8.508143s
# => << 33.9 (±3.0%) i/s - 171 in 5.054061s
Also we can run benchmark without any N specified because report blocks will be executed multiple times:
require 'benchmark'
require 'benchmark/ips'
Benchmark.ips do |r|
r.report("+ ") do
42 + 42
end
r.report("* ") do
42 * 42
end
end
# =>Calculating -------------------------------------
# => + 92249 i/100ms
# => * 91950 i/100ms
# =>-------------------------------------------------
# => + 6852082.4 (±6.8%) i/s - 34039881 in 4.998483s
# => * 6280292.3 (±5.5%) i/s - 31263000 in 4.996057s
Obviously benchmark_suite takes longer time but the results are more precise. Use this technique to test your class methods, algorithms and for having fun!
Happy benchmarking!
P.S. First time I heard about benchmark_suite was at Toster conference in Moscow during the presentation by Jon Leighton, maintainer of ActiveRecord. Slides
Comments
makaroni4 commented about 1 year ago
And one more example of testing classes:
releu commented about 1 year ago
Benchmarking always rule :)
eregon commented 5 months ago
Be aware the last example is mostly measuring yielding a block in a loop (the third form of #report can help a bit, but anyway addition is too fast and some implementations optimize it out as result is unused) and the one using N.times and benchmark-ips is inaccurate at least in error reporting (due to the so small number of iterations).
makaroni4 commented 5 months ago
@eregon so how can we run this comparison properly?
eregon commented 5 months ago
It is really hard to measure Fixnum#+ and #* because any execution to measure it will account very significant external factors, such as loops (if done in ruby, i+=1 will be in the loop, which is likely taking about the same time as 42+42), integer boxing/unboxing, etc. And to avoid aggressive optimizations, a result must be produced (and ideally used). So measuring precisely such fast operations with ruby tools is not realistic, but it is also very unlikely the bottleneck.
The example with String#+ / String#<< is mostly fine (without the N.times when using benchmark-ips), but the GC might take an important part of it and there are side effects so they can not be compared easily quantitatively.
makaroni4 commented 5 months ago
@eregon thanks for such a good explanations!
codesavvy commented 5 months ago
Nice post.. :D
killthekitten commented 4 months ago
@makaroni4 you've been mentioned at Ruby 5 - Episode 333 btw :D
makaroni4 commented 4 months ago
@killthekitten thx for it, I missed this one!