Page Content

Tutorials

What Is Profiling In Ruby & How It Works With Code Examples

Profiling in Ruby

Developers may find out where their application is spending the most time running code by using profiling, a crucial performance optimization approach. In order to analyze and speed up your application, profiling focusses on locating its slowest areas. The procedure entails submitting your code to a profiler, which generates a report outlining the execution time allocation.

How Profiling Works

The built-in Ruby profiler, accessible through the profile library, is usually used to profile a Ruby program.

Instrumentation: The Ruby profiler begins tracking and timing each successive method call when you include it in your application with require ‘profile’.

Mechanism: Setting the interpreter’s trace function (Kernel#set_trace_func) is how the profiler operates. A potent hook into the Ruby interpreter, the trace function is called anytime an event—like a method call or a return—occurs.

Reporting: Upon completion of the application, the profiler outputs a report to the standard error stream of the program. The execution is summarized in this report, which also displays the average and total time spent in each method as well as the frequency of calls to each method.

Analysis: The most time-consuming method calls are listed first in the results. You should address each call in turn, starting at the top of the report, in order to optimize your code. The method itself has to be optimized, or the code route needs to be changed to make the method less frequently invoked.

Please keep in mind that the profiler’s timing data is not very precise. The execution timings displayed are also far slower than when the program runs normally due to the significant overhead that profiling adds, but the relative values are typically useful for locating bottlenecks. When it comes to performance difficulties, the general lesson is that empirical data always outperforms conjecture.

Profiling with Code Examples

The example that follows shows how profiling might reveal a hidden performance issue when iterating through data.

Example 1: Original Inefficient Code (sequence_counter.rb)

The performance issue with this program’s use of nested loops with String#index to count letter sequences that contain the letters “a,” “b,” or “c.”

#!/usr/bin/env ruby
# sequence_counter.rb

# To profile, run: ruby -rprofile sequence_counter.rb

total = 0

# Generate all two-letter combinations from 'a' to 'z'
('a'..'z').each do |first|
  ('a'..'z').each do |second|
    seq = first + second
    if seq =~ /[abc]/   # Checks if the sequence includes a, b, or c
      total += 1
    end
  end
end

puts "Total: #{total}"

A report is produced by the profiler when this program is executed.

$ ruby sequence_counter.rb 
Total: 150 
  %   cumulative   self              self     total  time   seconds   seconds    calls  ms/call  ms/call  name  
  **54.55     0.30      0.30      702     0.43     0.50  Array#each** 
  32.73     0.48      0.18        1   180.00   550.00  Range#each 
  7.27     0.52      0.04     1952     0.02     0.02  String#index 
  ...

The analysis highlights the inner nested loop ([‘a’, ‘b’, ‘c’].each) as the main optimization target, demonstrating that Array#each uses 54.55% of the entire execution time (0.30 seconds of self time).

Example 2: Optimized Code (sequence_counter2.rb)

This performance problem can be resolved by substituting a single regular expression check (seq = /[abc]/), which is more effective for this operation, for the inner loop.

#!/usr/bin/env ruby
# sequence_counter2.rb
require 'profile'

total = 0
# Count the letter sequences containing an a, b, or c.
('a'..'zz').each {|seq| total +=1 if seq =~ /[abc]/ }
puts "Total: #{total}"

The profile report and runtime are significantly enhanced when the optimized version is used:

$ ruby sequence_counter2.rb 
Total: 150 
  %   cumulative   self              self     total  time   seconds   seconds    calls  ms/call  ms/call  name  
  **83.33     0.05      0.05        1    50.00    60.00  Range#each** 
  16.67     0.06      0.01      150     0.07     0.07  Fixnum#+ 
  ...

With a total completion time of just 0.05 seconds, the new version outperforms the original by a wide margin.

When profiling, stress-testing the operation on big datasets is advised to highlight performance issues, particularly if you are unsure which processes are slow.

Alternative Profiling Approach: Call Graph Analyzer

If the profiler identifies a frequently used method, such as Array#each, as the cause, you might need a tool that can display the lines of code that are causing the issue calls instead of just the method name. A Call Graph Analyzer or other specialized tool is needed for this.

You can create a custom CallTracker class that tracks “call” and “c-call” events using Kernel#set_trace_func. It uses Kernel#caller to retrieve the current call stack in order to track the call’s origin whenever an intriguing method is invoked. The report that results displays the most active lines of code that caused the sluggish method calls.

Running a call graph analyser, for example, could yield output indicating the origin of the thousands of calls to Array#each:

# 4930 calls to Array#each
# 1671 ./rubyful_soup.rb:715:in `pop_to_tag'
# 1631 ./rubyful_soup.rb:567:in `unknown_starttag'
# 1627 ./rubyful_soup.rb:751:in `smart_pop'
#    1 ./rubyful_soup.rb:510:in `feed'

Because of this detailed analysis, optimization efforts are precisely focused on the lines of code that require correction.

By providing you with an X-ray of your program’s burden, profiling enables you to identify the techniques and components that are carrying the most weight and any areas where they may be straining. This way, you can concentrate your efforts on addressing the structural flaws rather than speculating about the issue.

Read more on TDD In Ruby: Emphasizes The Creation Of Automated Tests

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index