Permalink
Browse files

Improve performance of string interpolation

This patch will add pre-allocation in string interpolation.
By this, unecessary capacity resizing is avoided.

For small strings, optimized `rb_str_resurrect` operation is
faster, so pre-allocation is done only when concatenated strings
are large.  `MIN_PRE_ALLOC_SIZE` was decided by experimenting with
local machine (x86_64-apple-darwin 16.5.0, Apple LLVM version
8.1.0 (clang - 802.0.42)).

String interpolation will be faster around 72% when large string is created.

* Before
  ```
  Calculating -------------------------------------
  Large string interpolation
                            1.276M (± 5.9%) i/s -      6.358M in   5.002022s
  Small string interpolation
                            5.156M (± 5.5%) i/s -     25.728M in   5.005731s
  ```

* After
  ```
  Calculating -------------------------------------
  Large string interpolation
                            2.201M (± 5.8%) i/s -     11.063M in   5.043724s
  Small string interpolation
                            5.192M (± 5.7%) i/s -     25.971M in   5.020516s
  ```

* Test code
  ```ruby
  require 'benchmark/ips'

  Benchmark.ips do |x|
    x.report "Large string interpolation" do |t|
      a = "Hellooooooooooooooooooooooooooooooooooooooooooooooooooo"
      b = "Wooooooooooooooooooooooooooooooooooooooooooooooooooorld"

      t.times do
        "#{a}, #{b}!"
      end
    end

    x.report "Small string interpolation" do |t|
      a = "Hello"
      b = "World"

      t.times do
        "#{a}, #{b}!"
      end
    end
  end
  ```

[Fix GH-1626]
From: Nao Minami <south37777@gmail.com>

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60320 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
  • Loading branch information...
nobu committed Oct 21, 2017
1 parent d808918 commit 80c50308f9db813e999367ec5d116e2d2be9f840
Showing with 24 additions and 4 deletions.
  1. +19 −4 string.c
  2. +5 −0 test/ruby/test_string.rb
View
@@ -2903,15 +2903,30 @@ rb_str_append(VALUE str, VALUE str2)
return rb_str_buf_append(str, str2);
}
#define MIN_PRE_ALLOC_SIZE 48
VALUE
rb_str_concat_literals(size_t num, const VALUE *strary)
{
VALUE str;
size_t i;
size_t i, s;
long len = 1;
if (UNLIKELY(!num)) return rb_str_new(0, 0);
if (UNLIKELY(num == 1)) return rb_str_resurrect(strary[0]);
for (i = 0; i < num; ++i) { len += RSTRING_LEN(strary[i]); }
if (LIKELY(len < MIN_PRE_ALLOC_SIZE)) {
str = rb_str_resurrect(strary[0]);
s = 1;
}
else {
str = rb_str_buf_new(len);
rb_enc_copy(str, strary[0]);
s = 0;
}
if (!num) return rb_str_new(0, 0);
str = rb_str_resurrect(strary[0]);
for (i = 1; i < num; ++i) {
for (i = s; i < num; ++i) {
const VALUE v = strary[i];
int encidx = ENCODING_GET(v);
View
@@ -620,6 +620,11 @@ def test_concat
assert_raise(RuntimeError) { 'foo'.freeze.concat('bar') }
end
def test_concat_literals
s="." * 50
assert_equal(Encoding::UTF_8, "#{s}x".encoding)
end
def test_count
a = S("hello world")
assert_equal(5, a.count(S("lo")))

0 comments on commit 80c5030

Please sign in to comment.