Skip to content

Comments

Use Thread.each_caller_location over caller_locations#144

Open
joshuay03 wants to merge 1 commit intobasecamp:masterfrom
joshuay03:use-thread-each-caller-location
Open

Use Thread.each_caller_location over caller_locations#144
joshuay03 wants to merge 1 commit intobasecamp:masterfrom
joshuay03:use-thread-each-caller-location

Conversation

@joshuay03
Copy link

@joshuay03 joshuay03 commented Jul 2, 2025

I'm doing some profiling of our app which uses v1.11.1 of this gem and the caller here shows up as both a time and allocation hotspot.

I was about to send a PR but noticed it has already been improved in #138 by switching to caller_locations. Here, I'd like to follow-up on that by addressing this comment to instead use Thread.each_caller_location, having done some benchmarking to confirm that it indeed provides better results in this case.

Benchmarks
# frozen_string_literal: true

require "benchmark/ips"

10_000.times do |i|
  define_method("my_method_#{i}") do
    send("my_method_#{i + 1}")
  end
end

LINES_TO_IGNORE = Regexp.new(100.times.map { |i| "my_method_#{i * 2}" }.join("|"))
LINES_TO_IGNORE_LENGTH = LINES_TO_IGNORE.to_s.split("|").length

def my_method_1000
  if $caller_locations
    last_line = caller_locations.detect do |loc|
      !loc.path.match?(LINES_TO_IGNORE)
    end
  elsif $caller_locations_ranged # just another experiment I tried
    last_line = nil
    range = 0...LINES_TO_IGNORE_LENGTH
    while !last_line && (locations = caller_locations(range))
      last_line = locations.detect do |loc|
        !loc.path.match?(LINES_TO_IGNORE)
      end
      range = range.last...(range.last + LINES_TO_IGNORE_LENGTH)
    end
  elsif $each_caller_location
    last_line = nil
    Thread.each_caller_location do |loc|
      if !loc.path.match?(LINES_TO_IGNORE)
        last_line = loc
        break
      end
    end
  else
    raise
  end
end

def alloc
  before = GC.stat[:total_allocated_objects]
  yield
  GC.stat[:total_allocated_objects] - before
end

Benchmark.ips do |x|
  x.report("caller_locations") do
    $caller_locations = true
    my_method_0
    $caller_locations = false
  end

  x.report("caller_locations ranged") do
    $caller_locations_ranged = true
    my_method_0
    $caller_locations_ranged = false
  end

  x.report("Thread.each_caller_location") do
    $each_caller_location = true
    my_method_0
    $each_caller_location = false
  end

  x.compare!
end

puts "\n"

puts "caller_locations allocations:"
$caller_locations = true
my_method_0 # warm up
allocs = alloc { my_method_0 }
$caller_locations = false
puts allocs

puts "caller_locations ranged allocations:"
$caller_locations_ranged = true
my_method_0 # warm up
allocs = alloc { my_method_0 }
$caller_locations_ranged = false
puts allocs

puts "Thread.each_caller_location allocations:"
$each_caller_location = true
my_method_0 # warm up
allocs = alloc { my_method_0 }
$each_caller_location = false
puts allocs
ruby 3.4.4 (2025-05-14 revision a38531fd3f) +YJIT +PRISM [arm64-darwin24]
Warming up --------------------------------------
    caller_locations   298.000 i/100ms
caller_locations ranged
                       349.000 i/100ms
Thread.each_caller_location
                       363.000 i/100ms
Calculating -------------------------------------
    caller_locations      3.124k (± 2.1%) i/s  (320.06 μs/i) -     15.794k in   5.057311s
caller_locations ranged
                          3.442k (±10.0%) i/s  (290.52 μs/i) -     17.101k in   5.063594s
Thread.each_caller_location
                          3.554k (± 3.1%) i/s  (281.38 μs/i) -     17.787k in   5.009613s

Comparison:
Thread.each_caller_location:     3554.0 i/s
caller_locations ranged:     3442.1 i/s - same-ish: difference falls within error
    caller_locations:     3124.4 i/s - 1.14x  slower


caller_locations allocations:
3010
caller_locations ranged allocations:
2108
Thread.each_caller_location allocations:
2004

Could I also please request a new release if this is merged? 🙏🏽

@joshuay03 joshuay03 marked this pull request as ready for review July 2, 2025 01:00
@joshuay03 joshuay03 moved this to In Progress / Pending Review in Open Source Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant