How to Benchmark my code in Ruby or Rails

Search for a command to run...

No comments yet. Be the first to comment.
PostgreSQL Upgrade and Database Restoration Guide To support the pgvector gem, I upgraded my local Postgres.app from v13 to v17 on macOS. Below is the full upgrade and restoration process for my project. Environment OS: macOS PostgreSQL: Upgraded f...

Introduction In Ruby on Rails applications, handling database transactions efficiently is crucial to maintaining data integrity. One common issue developers face is dealing with runtime errors caused by unpersisted changes when using the with_lock me...

Environment MacOS Sonoma 14.3 Original Ruby version: 2.7.8 Ruby Version Manager: RVM 1.29.12 Goal Upgrade Ruby from 2.7.8 to 3.0.6. Issues Description After successfully installing Ruby 3.0.6, I encountered difficulties running bundle install in...

It is common to see require 'something' in a Rails application. But what exactly does require do? What is require used for in Ruby? require is used to load external libraries or modules into your program. For example, Before you require the JSON modu...

In my daily job, I mainly use Rails framework. To make code readable and maintainable, we seldom write raw SQL in the codebase. Instead, we use Rails's ActiveRecord::QueryMethods module which helps developers write beautiful queries quickly. For exam...

Recently, I had a job to fix the slow code. Before diving into fixing the slow code, I used Scout APM to identify the performance bottlenecks, which helped me make sure where the slow code is.
To prove the new optimization solution is faster, in addition to checking the before-after response time in the log file, I think I need a more efficient tool to evaluate the solution.
Benchmarking is a way of measuring the performance of the code. It can answer the question like 'Which one is faster, A or B?'
Ruby has a benchmarking tool called Benchmark in its standard library. Benchmark module provides methods to measure and report the time used to execute Ruby code.
You can add a Ruby file first.
$ touch test.rb
You should require 'benchmark' to make sure you can use it. Next, put the code that you would like to test into the x.report { #put your code here } block.
require 'benchmark'
n = 100
Benchmark.bm do |x|
x.report('A') { for i in 1..n; a = '1'; end }
x.report('B') { n.times do ; a = '1'; end }
x.report('C') { 1.upto(n) do ; a = '1'; end }
end
Go back to the terminal and execute the file:
We can see the C plan is faster than the A or B plan.
$ ruby test.rb
user system total real
A 0.000026 0.000005 0.000031 ( 0.000024)
B 0.000018 0.000001 0.000019 ( 0.000018)
C 0.000016 0.000001 0.000017 ( 0.000017)
This report shows the user CPU time, system CPU time, the sum of the user and system CPU times, and the elapsed real time. The unit of time is seconds. (Source)
When searching the benchmarking tool, I found out another interesting gem called benchmark-ips.
An iterations per second enhancement to Benchmark.
Benchmark/ips will report the number of iterations per second for a given block of code. When analyzing the results, notice the percent of standard deviation which tells us how spread out our measurements are from the average. A high standard deviation could indicate the results having too much variability.
benchmark-ips uses standard deviation which is the concept of Statistics to analyze the result. The benefit is that we can literally make sure the result is significant/extreme significant/no significant difference.
In other words, take the result above for example:
C plan seems to be faster than the A or B plan, but it might not be the fact.
# n = 100
$ ruby test.rb
user system total real
A 0.000026 0.000005 0.000031 ( 0.000024)
B 0.000018 0.000001 0.000019 ( 0.000018)
C 0.000016 0.000001 0.000017 ( 0.000017)
Let's use benchmark-ips to analyze the result:
require "benchmark/ips"
n = 100
Benchmark.ips do |x|
x.report('A') { for i in 1..n; a = "1"; end }
x.report('B') { n.times do ; a = "1"; end }
x.report('C') { 1.upto(n) do ; a = "1"; end }
x.compare!
end
There is something interesting:
Warming up --------------------------------------
A 11.038k i/100ms
B 11.868k i/100ms
C 12.166k i/100ms
Calculating -------------------------------------
A 125.949k (± 3.9%) i/s - 629.166k in 5.003441s
B 126.662k (± 2.7%) i/s - 640.872k in 5.063567s
C 125.172k (± 6.3%) i/s - 632.632k in 5.076965s
Comparison:
B: 126662.1 i/s
A: 125949.4 i/s - same-ish: difference falls within error
C: 125172.4 i/s - same-ish: difference falls within error
From the perspective of Statistics, there is not a meaningful difference between the three plans. When I change n=100 to n=10000, the result is the same. I feel very surprised about the result. Very. (Please correct me if I misunderstood. 🙏)
It's simple. Just add require_relative '../../config/environment' so that you can use the model class you have built in your Rails App.
require 'benchmark/ips'
require_relative '../../config/environment'
transaction_ids = [1, 2, #....]
transactions = Transaction.where(id: transaction_ids)
Benchmark.ips do |x|
x.report('new') do
transactions.map(&:some_column).compact
end
x.report('old') do
transaction_records = transactions.
includes(:a_table, :b_table, :c_table).order(created_at: :desc)
transaction_records.map(&:some_column).compact
end
x.compare!
end