Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
111: Profiling Ruby Code with ruby-prof
Imagine you're managing a busy professional kitchen during the Friday night rush. The customers are complaining that their food is taking too long to arrive. Now, you could just yell at everyone to "work faster," but that's a waste of energy. You don't actually know why it's slow. Is the prep cook taking forever to chop the onions? Is the grill chef struggling with the steaks? Or is the plating station the actual bottleneck where everything piles up?
To fix this, you wouldn't guess. You'd stand there with a stopwatch and time exactly how long a dish spends at each station. That's exactly what profiling is in Ruby. Instead of guessing that a specific loop is "probably the slow part," we use a profiler to tell us exactly how many milliseconds were spent in each method.
In the Ruby world, ruby-prof is the stopwatch of choice. It doesn't just tell you the total runtime of your script; it breaks down the execution into a call graph, showing you which methods are the "bottlenecks" and which ones are just passengers.
Getting ruby-prof into your project
First, you'll need the gem. I usually add this to my :development group in the Gemfile because you definitely don't want the profiling overhead running in production. It would slow your app to a crawl.
gem install ruby-prof
Wrapping your code for measurement
You don't want to profile your entire boot process (like loading Rails or your gems), because that will clutter your report with noise. Instead, you wrap only the specific block of code you suspect is lagging. Let's look at a real-world scenario: a report generator that calculates user spending habits. I've written this one intentionally poorly to give us something to find.
require 'ruby-prof'
class SpendingReport
def initialize(users)
@users = users
end
def generate
@users.map do |user|
# This is a hidden performance killer
calculate_total_spend(user)
end
end
def calculate_total_spend(user)
# Simulating a slow operation, like an inefficient DB query
sleep(0.01)
user[:transactions].sum { |t| t[:amount] }
end
end
# Setup some dummy data
users = Array.new(100) { { name: "User", transactions: [{amount: 10}, {amount: 20}] } }
report = SpendingReport.new(users)
# Here is the profiling magic
RubyProf.start
report.generate
result = RubyProf.stop
Deciphering the Wall Time
Once you've stopped the profiler, you have a result object, but it's not human-readable yet. You need a printer. I almost always use the FlatPrinter for a quick sanity check, as it gives you a straightforward list of where the time went.
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
When you look at the output, you'll see columns like Self and Total. This is where most people get confused. Pay close attention: Self time is the time spent exclusively inside that method. Total time is the time spent in that method plus all the other methods it called. If generate has a high total time but a low self time, it means the slowness isn't in the loop itself, but in the method the loop is calling (in our case, calculate_total_spend).
Visualizing the Call Stack
If your app is complex, a flat list isn't enough. You need to see the "who called whom" relationship. For this, I use the GraphPrinter, which generates a .dot file you can view as an image. It's a bit more setup, but it's the only way to spot deep architectural bottlenecks.
printer = RubyProf::GraphPrinter.new(result)
printer.print(File.open("profile_graph.dot", "w"))
I've spent hours staring at code thinking a specific regex was the problem, only to run a profile and realize the app was spending 90% of its time in a logger that I had forgotten to turn off. Don't trust your gut; trust the profiler.
📋 Practical Task
Optimizing the Slow Portfolio Aggregator
You've been handed a script that aggregates stock portfolios for a group of clients. The current implementation is painfully slow. Your goal is to use ruby-prof to identify the exact method causing the bottleneck and then refactor it to improve performance.
The Scenario:
The PortfolioManager class iterates through a list of portfolios. Inside, it calls a method fetch_current_price. Currently, this method is simulating a network request using sleep(0.05) for every single single asset in every single portfolio. This is an "N+1" style performance disaster.
# portfolio_aggregator.rb
require 'ruby-prof'
class PortfolioManager
def initialize(portfolios)
@portfolios = portfolios
end
def total_value_report
@portfolios.map do |portfolio|
portfolio_sum(portfolio)
end
end
def portfolio_sum(portfolio)
portfolio[:assets].sum do |asset|
fetch_current_price(asset) * asset[:quantity]
end
end
def fetch_current_price(asset)
sleep(0.05) # Simulating a slow API call
100.0
end
end
# Setup: 20 portfolios with 10 assets each
data = Array.new(20) { { assets: Array.new(10) { { name: "Stock", quantity: 5 } } } }
manager = PortfolioManager.new(data)
RubyProf.start
manager.total_value_report
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
Your Task:
- Run the code as provided. Identify the method with the highest Self Time in the output.
- Refactor the
PortfolioManagerto reduce the number of calls tofetch_current_price. (Hint: Think about "bulk fetching" or caching the price of the asset so you don't sleep for every single item). - Run the profiler again and verify that the Total Time for the
total_value_reportmethod has dropped significantly.
There are no comments for now.