Ruby Array concat versus + speed?

后端 未结 4 542
萌比男神i
萌比男神i 2021-02-12 06:00

I did small performance test of Ruby\'s array concat() vs + operation and concat() was way too fast.

I however am not clear on why

相关标签:
4条回答
  • 2021-02-12 06:36

    The OP's question, as noted in other answers, is comparing two operators that perform different purposes. One, concat, which is destructive to (mutates) the original array, and + which is non-destructive (pure functional, no mutation).

    I came here looking for a more comparable test, not realizing at the time, that concat was destructive. In case it's useful to others looking to compare two purely functional, non-destructive operations, here is a benchmark of array addition (array1 + array2) vs array expansion ([*array1, *array2]). Both, as far as I'm aware, result in 3 arrays being created: 2 input arrays, 1 new resulting array.

    Hint: + wins.

    Code

    # a1 is a function producing a random array to avoid caching
    a1 = ->(){ [rand(10)] }
    a2 = [1,2,3]
    n = 10_000_000
    Benchmark.bm do |b|
      b.report('expand'){ n.times{ [*a1[], *a2] } }
      b.report('add'){ n.times{ a1[]+a2 } }
    end
    

    Result

    user     system      total        real
    expand  9.970000   0.170000  10.140000 ( 10.151718)
    add  7.760000   0.020000   7.780000 (  7.792146)
    
    0 讨论(0)
  • 2021-02-12 06:37

    If you're going to run benchmarks, take advantage of prebuilt tools and reduce the test to the minimum necessary to test what you want to know.

    Starting with Fruity, which provides a lot of intelligence to its benchmarking:

    require 'fruity'
    
    compare do
      plus { [] + [4, 5] }
      concat { [].concat([4, 5]) }
    end
    # >> Running each test 32768 times. Test will take about 1 second.
    # >> plus is similar to concat
    

    When things are close enough to not really worry about, Fruity will tell us they're "similar".

    At that point Ruby's built-in Benchmark class can help:

    require 'benchmark'
    
    N = 10_000_000
    3.times do
      Benchmark.bm do |b|
        b.report('plus')  { N.times { [] + [4, 5] }}
        b.report('concat') { N.times { [].concat([4,5]) }}
      end
    end
    # >>        user     system      total        real
    # >> plus  1.610000   0.000000   1.610000 (  1.604636)
    # >> concat  1.660000   0.000000   1.660000 (  1.668227)
    # >>        user     system      total        real
    # >> plus  1.600000   0.000000   1.600000 (  1.598551)
    # >> concat  1.690000   0.000000   1.690000 (  1.682336)
    # >>        user     system      total        real
    # >> plus  1.590000   0.000000   1.590000 (  1.593757)
    # >> concat  1.680000   0.000000   1.680000 (  1.684128)
    

    Notice the different times. Running a test once can result in misleading results, so run them several times. Also, make sure your loops result in a time that isn't buried in background noise caused by processes kicking off.

    0 讨论(0)
  • 2021-02-12 06:49

    The answer lies in Ruby's underlying C implementation of the + operator and the concat methods.

    Array#+

    rb_ary_plus(VALUE x, VALUE y)
    {
        VALUE z;
        long len, xlen, ylen;
    
        y = to_ary(y);
        xlen = RARRAY_LEN(x);
        ylen = RARRAY_LEN(y);
        len = xlen + ylen;
        z = rb_ary_new2(len);
    
        ary_memcpy(z, 0, xlen, RARRAY_CONST_PTR(x));
        ary_memcpy(z, xlen, ylen, RARRAY_CONST_PTR(y));
        ARY_SET_LEN(z, len);
        return z;
    }
    

    Array#concat

    rb_ary_concat(VALUE x, VALUE y)
    {
        rb_ary_modify_check(x);
        y = to_ary(y);
        if (RARRAY_LEN(y) > 0) {
            rb_ary_splice(x, RARRAY_LEN(x), 0, y);
        }
        return x;
    }
    

    As you can see, the + operator is copying the memory from each array, then creating and returning a third array with the contents of both. The concat method is simply splicing the new array into the original one.

    0 讨论(0)
  • 2021-02-12 06:51

    According to the Ruby docs, the difference is:

    Array#+ :

    Concatenation — Returns a new array built by concatenating the two arrays together to produce a third array.

    Array#concat :

    Array#concat : Appends the elements of other_ary to self.

    So the + operator will create a new array each time it is called (which is expensive), while concat only appends the new element.

    0 讨论(0)
提交回复
热议问题