Project

General

Profile

Feature #12025

Reduce minimum string buffer size from 128 to 127

Added by jeremyevans0 (Jeremy Evans) about 1 year ago. Updated 8 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:73493]

Description

This changes the minimum buffer size for string buffers from 128 to
127. The underlying C buffer is always 1 more than the ruby buffer,
so this changes the actual amount of memory used for the minimum
string buffer from 129 to 128. This makes it much easier on the
malloc implementation, as evidenced by the following code (note that
time -l is used here, but Linux systems may need time -v).

$ cat bench_mem.rb
i = ARGV.first.to_i
Array.new(1000000){" " * i}
$ /usr/bin/time -l ruby bench_mem.rb 128
        3.10 real         2.19 user         0.46 sys
    289080  maximum resident set size
     72673  minor page faults
        13  block output operations
        29  voluntary context switches
$ /usr/bin/time -l ruby bench_mem.rb 127
        2.64 real         2.09 user         0.27 sys
    162720  maximum resident set size
     40966  minor page faults
         2  block output operations
         4  voluntary context switches

To try to ensure a power-of-2 growth, when a ruby string capacity
needs to be increased, after doubling the capacity, add one. This
ensures the ruby capacity will be odd, which means actual amount
of memory used will be even, which is probably better than the
current case of the ruby capacity being even and the actual amount
of memory used being odd.

A very similar patch was proposed 4 years ago in feature #5875. It
ended up being rejected, because no performance increase was shown.
One reason for that is that ruby does not use STR_BUF_MIN_SIZE
unless rb_str_buf_new is called, and that previously did not have
a ruby API, only a C API, so unless you were using a C extension
that called it, there would be no performance increase.

With the recently proposed feature #12024, String.buffer is added,
which is a ruby API for creating string buffers. Using
String.buffer(100) wastes much less memory with this patch, as the
malloc implementation can more easily deal with the power-of-2
sized memory usage. As measured above, memory usage is 44% less,
and performance is 17% better.

0001-Reduce-minimum-string-buffer-size-from-128-to-127.patch View (2.72 KB) jeremyevans0 (Jeremy Evans), 01/26/2016 09:04 PM

Associated revisions

Revision 55686
Added by normal 8 months ago

string.c: reduce malloc overhead for default buffer size

  • string.c (STR_BUF_MIN_SIZE): reduce from 128 to 127 [Feature #12025]
  • string.c (rb_str_buf_new): adjust for above reduction

From Jeremy Evans code@jeremyevans.net:

This changes the minimum buffer size for string buffers from 128 to
127. The underlying C buffer is always 1 more than the ruby buffer,
so this changes the actual amount of memory used for the minimum
string buffer from 129 to 128. This makes it much easier on the
malloc implementation, as evidenced by the following code (note that
time -l is used here, but Linux systems may need time -v).

$ cat bench_mem.rb
i = ARGV.first.to_i
Array.new(1000000){" " * i}
$ /usr/bin/time -l ruby bench_mem.rb 128
3.10 real 2.19 user 0.46 sys
289080 maximum resident set size
72673 minor page faults
13 block output operations
29 voluntary context switches
$ /usr/bin/time -l ruby bench_mem.rb 127
2.64 real 2.09 user 0.27 sys
162720 maximum resident set size
40966 minor page faults
2 block output operations
4 voluntary context switches

To try to ensure a power-of-2 growth, when a ruby string capacity
needs to be increased, after doubling the capacity, add one. This
ensures the ruby capacity will be odd, which means actual amount
of memory used will be even, which is probably better than the
current case of the ruby capacity being even and the actual amount
of memory used being odd.

A very similar patch was proposed 4 years ago in feature #5875. It
ended up being rejected, because no performance increase was shown.
One reason for that is that ruby does not use STR_BUF_MIN_SIZE
unless rb_str_buf_new is called, and that previously did not have
a ruby API, only a C API, so unless you were using a C extension
that called it, there would be no performance increase.

With the recently proposed feature #12024, String.buffer is added,
which is a ruby API for creating string buffers. Using
String.buffer(100) wastes much less memory with this patch, as the
malloc implementation can more easily deal with the power-of-2
sized memory usage. As measured above, memory usage is 44% less,
and performance is 17% better.

History

#1 [ruby-core:73496] Updated by normalperson (Eric Wong) about 1 year ago

merch-redmine@jeremyevans.net wrote:

String.buffer(100) wastes much less memory with this patch, as the
malloc implementation can more easily deal with the power-of-2
sized memory usage. As measured above, memory usage is 44% less,
and performance is 17% better.

None of jemalloc, dlmalloc 2.8.6, or glibc (dlmalloc 2.7.x-based) are
power-of-2 allocators. I was not able to measure a difference with any
of those.

But yeah, I could see this being an improvement for power-of-2 malloc
implementations out there (OpenBSD?). I don't think your change
would be harmful for jemalloc/dlmalloc/glibc users, either.

#2 [ruby-core:73510] Updated by naruse (Yui NARUSE) about 1 year ago

  • Description updated (diff)

#3 [ruby-core:73533] Updated by naruse (Yui NARUSE) about 1 year ago

With glibc as Eric says it won't save memory so much.
But with jemalloc, whose next allocation size of 128 is 192, saves some memory.
Darwin also saves 6%.

memo:
https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919/
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
http://www.slideshare.net/kosaki55tea/glibc-malloc in Japanese

#4 [ruby-core:76371] Updated by jeremyevans0 (Jeremy Evans) 8 months ago

It's been over 5 months since this issue was created. Since it looks like both Eric and Yui agree that this won't cause harm to any systems and can benefit some systems, could it be accepted?

#5 Updated by Anonymous 8 months ago

  • Status changed from Open to Closed

Applied in changeset r55686.


string.c: reduce malloc overhead for default buffer size

  • string.c (STR_BUF_MIN_SIZE): reduce from 128 to 127 [Feature #12025]
  • string.c (rb_str_buf_new): adjust for above reduction

From Jeremy Evans code@jeremyevans.net:

This changes the minimum buffer size for string buffers from 128 to
127. The underlying C buffer is always 1 more than the ruby buffer,
so this changes the actual amount of memory used for the minimum
string buffer from 129 to 128. This makes it much easier on the
malloc implementation, as evidenced by the following code (note that
time -l is used here, but Linux systems may need time -v).

$ cat bench_mem.rb
i = ARGV.first.to_i
Array.new(1000000){" " * i}
$ /usr/bin/time -l ruby bench_mem.rb 128
3.10 real 2.19 user 0.46 sys
289080 maximum resident set size
72673 minor page faults
13 block output operations
29 voluntary context switches
$ /usr/bin/time -l ruby bench_mem.rb 127
2.64 real 2.09 user 0.27 sys
162720 maximum resident set size
40966 minor page faults
2 block output operations
4 voluntary context switches

To try to ensure a power-of-2 growth, when a ruby string capacity
needs to be increased, after doubling the capacity, add one. This
ensures the ruby capacity will be odd, which means actual amount
of memory used will be even, which is probably better than the
current case of the ruby capacity being even and the actual amount
of memory used being odd.

A very similar patch was proposed 4 years ago in feature #5875. It
ended up being rejected, because no performance increase was shown.
One reason for that is that ruby does not use STR_BUF_MIN_SIZE
unless rb_str_buf_new is called, and that previously did not have
a ruby API, only a C API, so unless you were using a C extension
that called it, there would be no performance increase.

With the recently proposed feature #12024, String.buffer is added,
which is a ruby API for creating string buffers. Using
String.buffer(100) wastes much less memory with this patch, as the
malloc implementation can more easily deal with the power-of-2
sized memory usage. As measured above, memory usage is 44% less,
and performance is 17% better.

#6 [ruby-core:76372] Updated by normalperson (Eric Wong) 8 months ago

merch-redmine@jeremyevans.net wrote:

It's been over 5 months since this issue was created. Since it looks
like both Eric and Yui agree that this won't cause harm to any systems
and can benefit some systems, could it be accepted?

Thanks for the reminder, committed as r55686

/me is forgetfulperson :x

#7 [ruby-core:76375] Updated by ngoto (Naohisa Goto) 8 months ago

Due to the "+ 1" in string.c:2593 inserted in r55686, integer overflow may occur if capa == LONG_MAX / 2.

        while (total > capa) {
        if (capa > LONG_MAX / 2) {
            capa = (total + 4095) / 4096 * 4096;
            break;
        }
        capa = 2 * capa + 1;
        }

I also think that termlen should be used, instead of "+ 1".

#9 [ruby-core:76377] Updated by ngoto (Naohisa Goto) 8 months ago

In r55692, integer overflow is fixed, and termlen is used.

#10 [ruby-core:76380] Updated by normalperson (Eric Wong) 8 months ago

ngotogenome@gmail.com wrote:

In r55692, integer overflow is fixed, and termlen is used.

Good catch, thank you

Also available in: Atom PDF