Skip to content

Instantly share code, notes, and snippets.

@serg-kovalev
Last active February 3, 2025 15:50
Show Gist options
  • Save serg-kovalev/0c775a41a5141b17f14eea4d6bfb732a to your computer and use it in GitHub Desktop.
Save serg-kovalev/0c775a41a5141b17f14eea4d6bfb732a to your computer and use it in GitHub Desktop.
require 'objspace'
# Helper function to format memory size into human-readable units
def human_readable_size(size_in_bytes)
return "#{size_in_bytes} bytes" if size_in_bytes < 1024
return "#{(size_in_bytes / 1024.0).round(2)} KB" if size_in_bytes < 1024 * 1024
return "#{(size_in_bytes / 1024.0 / 1024.0).round(2)} MB" if size_in_bytes < 1024 * 1024 * 1024
return "#{(size_in_bytes / 1024.0 / 1024.0 / 1024.0).round(2)} GB"
end
def prof_memory(top_x = 20)
raise ArgumentError, "prof_memory requires a block" unless block_given?
# Ensure GC is run before starting the profiling
GC.start(full_mark: true, immediate_sweep: true)
# Start tracking object allocations
ObjectSpace.trace_object_allocations_start
# Track memory usage and object count before the block
memory_before = GC.stat[:total_allocated_objects]
objects_before = Hash.new(0)
ObjectSpace.each_object do |obj|
begin
objects_before[obj.class] += 1 if obj.respond_to?(:class)
rescue StandardError
next
end
end
yield
# Track memory usage and object count after the block
memory_after = GC.stat[:total_allocated_objects]
objects_after = Hash.new(0)
ObjectSpace.each_object do |obj|
begin
objects_after[obj.class] += 1 if obj.respond_to?(:class)
rescue StandardError
next
end
end
# Calculate memory difference
memory_diff = memory_after - memory_before
# Calculate object count differences
object_count_diff = 0
objects_after.each do |klass, count|
object_count_diff += count - objects_before[klass]
end
# Find the top newly allocated objects by class
object_diff = objects_after.each_with_object({}) do |(klass, count), diff|
diff[klass] = count - objects_before[klass] if count > objects_before[klass]
end.sort_by { |_, count| -count }.first(top_x)
# Track memory usage of objects
memory_usage = []
ObjectSpace.each_object(Object) do |obj|
size = ObjectSpace.memsize_of(obj)
next if size.nil? || size <= 0
memory_usage << { class: obj.class.name, id: obj.object_id, size: size }
memory_usage.sort_by! { |entry| entry[:size] } if memory_usage.size > top_x
memory_usage.shift if memory_usage.size > top_x
end
memory_usage.sort_by! { |entry| -entry[:size] }
# Output results
puts "Objects before: #{memory_before}"
puts "Objects after: #{memory_after}"
puts "Objects diff: #{memory_diff.positive? ? '+' : ''}#{memory_diff}"
puts "Object count diff: #{object_count_diff}"
puts "\nTop #{top_x} newly allocated objects by class:"
object_diff.each { |klass, count| puts "#{klass}: #{count}" }
puts "\nTop #{top_x} largest objects:"
memory_usage.each_with_index do |entry, index|
puts "#{index + 1}. #{entry[:class]} object of id #{entry[:id]} is using #{human_readable_size(entry[:size])}"
end
end
## Usage
prof_memory(20) do
# Code you want to profile
100.times { |i| "string_#{i}".dup }
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment