Last active
February 3, 2025 15:50
-
-
Save serg-kovalev/0c775a41a5141b17f14eea4d6bfb732a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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