Technical writings of Shkrt
When coming to a speed of Ruby applications, the first thing we going to do is to identify application’s bottlenecks. Usually, such a task is not imaginable without a benchmarking tool. In this article, I intend to make an overview of typical tools for simplest ruby benchmarking tasks.
The first and most obvious tool is benchmark
available from within Ruby’s standard library. Let’s try it to see performance
difference between sort_by
and shuffle
methods to shuffle the elements of an array of 1_000_000 elements.
As we can see, with this amount of data shuffling via Array#shuffle
is about 30 times more efficient than the using Array.sort_by
.
Another example will involve the performance comparison between String#include
and Regexp#match
to assert whether the given
string includes the provided substring or not.
This example shows that String#include? is about 2.6 times more performant than Regexp.match in this particular case. But that is not very precise measurement. If we try to wrap our code blocks into loops, we would get different results everytime we run the benchmark:
You can try this yourself, include?
would always win, but the numbers will differ significantly between the benchmarks.
So, to make precise measurements, we have to choose the number of iterations.
Fortunately, the Ruby ecosystem has another tool, that makes this choice for us - it is the evanphx/benchmark-ips, which is the
enhanced version of benchmark.
Let’s try our previous example with benchmark-ips
:
This kind of benchmark operates with iterations per second
measurement and therefore its output shows the difference
between compared code blocks in a more clear and understandable way. Obviously, this tool is more popular for these reasons.
Let’s compare the performance difference between Struct, OpenStruct, PORO, and a Hash using benchmark/ips
, which now will
help us to build a really fancy benchmark report.
This script produces a very descriptive report about the performance of each data structure in this simple scenario. As you may see from its output(obviously, truncated), the OpenStruct is really a performance destroyer, and Struct is as performant as the Plain Old Ruby Object, when it comes to reading or initialization.
Initialization
Comparison:
Struct: 5758806.7 i/s
PORO: 5598489.7 i/s - same-ish: difference falls within error
Hash: 3143382.6 i/s - 1.83x slower
ostruct: 1150810.4 i/s - 5.00x slower
Reading
Comparison:
PORO: 13365686.7 i/s
Hash#[]: 12988414.6 i/s - same-ish: difference falls within error
Struct: 12721531.5 i/s - same-ish: difference falls within error
Hash#fetch: 10978007.0 i/s - 1.22x slower
ostruct: 7877572.2 i/s - 1.70x slower
Writing
Comparison:
PORO: 12676271.4 i/s
Hash#[]: 10976062.0 i/s - 1.15x slower
Struct: 10609637.3 i/s - 1.19x slower
ostruct: 5380540.4 i/s - 2.36x slower
Suggested Reading:
[ruby
]