Feature #15349
closedUse a shared array for the `duparray` instruction
Description
In this example code:
def foo
[1, 2, 3, 4]
end
The array literal uses a duparray instruction. Before this patch,
rb_ary_resurrect would malloc and memcpy a new array buffer. This
patch changes rb_ary_resurrect to use ary_make_partial so that the
new array object shares the underlying buffer with the array stored in
the instruction sequences.
Before this patch, the new array object is not shared:
$ ruby -r objspace -e'p ObjectSpace.dump([1, 2, 3, 4])'
"{\"address\":\"0x7fa2718372d0\", \"type\":\"ARRAY\", \"class\":\"0x7fa26f8b0010\", \"length\":4, \"memsize\":72, \"flags\":{\"wb_protected\":true}}\n"
After this patch:
$ ./ruby -r objspace -e'p ObjectSpace.dump([1, 2, 3, 4])'
"{\"address\":\"0x7f9a76883638\", \"type\":\"ARRAY\", \"class\":\"0x7f9a758af900\", \"length\":4, \"shared\":true, \"references\":[\"0x7f9a768837c8\"], \"memsize\":40, \"flags\":{\"wb_protected\":true}}\n"
I wrote a test program:
def foo
[1, 2, 3, 4]
end
list = []
10000.times { list << foo }
GC.start
puts "ready #{$$}"
system "malloc_history #{$$} -allEvents > #{RUBY_VERSION}-#{ARGV[0]}.log" # MacOS specific
This test program uses a MacOS specific tool to get a list of all malloc / free calls (should be able to do the same with valgrind, I just don't know the command). I compared trunk, trunk + this patch, and Ruby 2.5.3. All tests disable RubyGems.
Here is a graph of the results:
The X axis is sample number, and the Y axis is total bytes the process is using at that sample. Each sample is a call to malloc, so the closer the line is to the origin (0, 0), the better (it means fewer samples and less live memory).
After this patch there are fewer calls to malloc than in either trunk or 2.5 for this test program.
If I modify the test program to be like this:
def foo
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
]
end
list = []
10000.times { list << foo }
GC.start
puts "ready #{$$}"
system "malloc_history #{$$} -allEvents > #{RUBY_VERSION}-#{ARGV[0]}.log"
The difference in memory usage becomes more pronounced:
I noticed that strings already use this technique:
$ ruby -v --disable-gems -robjspace -e'p ObjectSpace.dump("abcdefghijklmnopqrstuvwxyz")'
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
"{\"address\":\"0x00007fad18051528\", \"type\":\"STRING\", \"class\":\"0x00007fad180d3c08\", \"shared\":true, \"encoding\":\"UTF-8\", \"references\":[\"0x00007fad18051640\"], \"memsize\":40, \"flags\":{\"wb_protected\":true}}\n"
I was going to apply this patch, but it modifies rb_ary_resurrect
and I wanted to double check. rb_str_resurrect
will create shared strings, so it seems OK for rb_ary_resurrect
to create shared arrays, IMO.
Does anyone have opinions on this? If not, I'll just apply the patch.
(Also I noticed trunk seems to allocate a lot more memory at boot)
Thanks!
Files
Updated by normalperson (Eric Wong) over 6 years ago
tenderlove@ruby-lang.org wrote:
I was going to apply this patch, but it modifies
rb_ary_resurrect
and I wanted to double check.rb_str_resurrect
will create shared strings, so it seems OK forrb_ary_resurrect
to create shared arrays, IMO.
It's probably fine; I would not expect us to have bugs in our
shared array handling code at this point.
Does anyone have opinions on this? If not, I'll just apply the patch.
(Also I noticed trunk seems to allocate a lot more memory at boot)
Looks like transient heap (only checked miniruby, so no gems loaded).
Disabling transient heap (below) seems to help, but real-world results
would likely be different:
diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h
index 58e28d2c9f..52a263ffe2 100644
--- a/include/ruby/ruby.h
+++ b/include/ruby/ruby.h
@@ -1014,7 +1014,7 @@ struct RString {
((ptrvar) = RSTRING(str)->as.heap.ptr, (lenvar) = RSTRING(str)->as.heap.len))
#ifndef USE_TRANSIENT_HEAP
-#define USE_TRANSIENT_HEAP 1
+#define USE_TRANSIENT_HEAP 0
#endif
enum ruby_rarray_flags {
Updated by tenderlovemaking (Aaron Patterson) over 6 years ago
normalperson (Eric Wong) wrote:
tenderlove@ruby-lang.org wrote:
I was going to apply this patch, but it modifies
rb_ary_resurrect
and I wanted to double check.rb_str_resurrect
will create shared strings, so it seems OK forrb_ary_resurrect
to create shared arrays, IMO.It's probably fine; I would not expect us to have bugs in our
shared array handling code at this point.
Ok, I'll apply the patch.
Does anyone have opinions on this? If not, I'll just apply the patch.
(Also I noticed trunk seems to allocate a lot more memory at boot)
Looks like transient heap (only checked miniruby, so no gems loaded).
Disabling transient heap (below) seems to help, but real-world results
would likely be different:
I tried disabling theap and re-running the test. It looks like disabling theap brings the memory usage back to what Ruby 2.5 does:
I tried a similar test, but booting a Rails application. It looks like theap just adds a constant overhead to the application:
Disabling theap makes memory usage on 2.6 lower than 2.5, where enabling theap keeps it constantly above 2.5 levels.
I'll open another ticket for this issue.
Updated by tenderlovemaking (Aaron Patterson) over 6 years ago
- Status changed from Open to Closed
I committed this in r66095, not sure why it didn't close this.
Updated by k0kubun (Takashi Kokubun) over 6 years ago
- Related to Bug #15479: Array#reject! modifies literal Array added