Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby Performance note #17

Open
hungle00 opened this issue Mar 2, 2024 · 0 comments
Open

Ruby Performance note #17

hungle00 opened this issue Mar 2, 2024 · 0 comments
Labels
ruby About Ruby and Rails

Comments

@hungle00
Copy link
Owner

hungle00 commented Mar 2, 2024

1. Memory consumption and GC

Print Process RSS

On Linux and Mac OS X you can get RSS from the ps command:

`ps -o rss= -p #{Process.pid}`.to_i
# mb
`ps -o rss= -p #{Process.pid}`.to_i / 1024

Garbage Collector

Compare two same versions of code, one with GC works and one with GC disable

require "benchmark"

num_rows = 100000
num_cols = 10
data = Array.new(num_rows) { Array.new(num_cols) { "x"*1000 } }

GC.disable # just add disable GC in version 2
time = Benchmark.realtime do
  csv = data.map { |row| row.join(",") }.join("\n")
end

puts time.round(2)

Now let’s run this and compare our measurements between 2 versions. On my computer, version 1 takes 2.72s and version 2 takes 1.91s. The percent of time spent in GC is about 30%. We can see our program spends quite a lot of its execution time in the garbage collector.

Of course, we can not disable GC altogether for better performance. Turning off GC significantly increases peak memory consumption. The operating system may run out of memory or start swapping.
We will use RSS to measure object memory before and after GC kick-in

num_rows = 100000
num_cols = 10
data = Array.new(num_rows) { Array.new(num_cols) { "x"*1000 } }

puts "%d MB" % (`ps -o rss= -p #{Process.pid}`.to_i/1024)
GC.disable

data.map { |row| row.join(",") }.join("\n")

puts "%d MB" % (`ps -o rss= -p #{Process.pid}`.to_i/1024)
920 MB
2513 MB

2. Object Memory

Objects

In Ruby everything is an object that’s internally represented as the RVALUE struct. sizeof(RVALUE) is machine dependent:
For example, 40 bytes in 64-bit architecture. Most modern computers are 64-bit, so we’ll assume that one object costs us 40 bytes of memory to create.

A Ruby object can store only a limited amount of data, up to 40 bytes. Slightly less than half of that is required for upkeep. All data that does not fit into the object itself is dynamically allocated outside of the Ruby heap. When the object is swept by GC, the memory is freed.

ObjectSpace library

For example, a Ruby string stores only 23 bytes in the RSTRING object on a 64-bit system. When the string length becomes larger than 23 bytes, Ruby allocates additional memory for it.
We can see how much by calling ObjectSpace#memsize_of, for example, like this:

require 'objspace'

ObjectSpace.memsize_of(Object.new)
# => 40
str = 'x' * 23
ObjectSpace.memsize_of(str)
# => 40
str = 'x' * 24
ObjectSpace.memsize_of(str)
# => 65

https://ivoanjo.me/blog/2021/02/11/looking-into-array-memory-usage/

@hungle00 hungle00 added the ruby About Ruby and Rails label Mar 2, 2024
@hungle00 hungle00 changed the title Ruby memory notes Ruby Performance note Mar 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ruby About Ruby and Rails
Projects
None yet
Development

No branches or pull requests

1 participant