Подтвердить что ты не робот

Ruby Array concat против + скорости?

Я выполнил небольшой тест производительности работы с массивом Ruby concat() vs +, а concat() был слишком быстрым.

Я не знаю, почему concat() так быстро?

Может ли кто-нибудь помочь здесь?

Это код, который я использовал:

t = Time.now
ar = []
for i in 1..10000
ar = ar + [4,5]
end
puts "Time for + " + (Time.now - t).to_s 


t = Time.now
ar = []
for i in 1..10000
ar.concat([4,5])
end
puts "Time for concat " + (Time.now - t).to_s 
4b9b3361

Ответ 1

В соответствии с Ruby docs разница заключается в следующем:

Массив # +:

Конкатенация - возвращает новый массив, созданный объединением двух массивов вместе для создания третьего массива.

Массив # concat:

Array # concat: добавляет элементы other_ary к себе.

Таким образом, оператор + будет создавать новый массив каждый раз, когда он вызывается (что дорого), а concat добавляет только новый элемент.

Ответ 2

Ответ лежит на Ruby, лежащем в основе реализации C оператора + и методов concat.

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;
}

Как вы можете видеть, оператор + копирует память из каждого массива, а затем создает и возвращает третий массив с содержимым обоих. Метод concat просто соединяет новый массив с исходным.

Ответ 3

Если вы собираетесь запускать тесты, используйте заранее подготовленные инструменты и уменьшите тест до минимума, необходимого для проверки того, что вы хотите знать.

Начиная с Fruity, который обеспечивает большой интеллект для его бенчмаркинга:

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

Когда все будет достаточно близко, чтобы не волноваться, Фрутис скажет нам, что они "похожи".

В этот момент Ruby встроенный Benchmark класс может помочь:

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)

Обратите внимание на разное время. Запуск теста один раз может привести к вводящим в заблуждение результатам, поэтому запустите их несколько раз. Кроме того, убедитесь, что ваши петли приводят к тому, что время не затухает в фоновом шуме, вызванном запуском процессов.

Ответ 4

Вопрос OP, как указано в других ответах, сравнивает двух операторов, которые выполняют разные цели. Один, concat, который разрушает исходный массив (мутирует) и +, который является неразрушающим (чистый функционал, без мутации).

Я пришел сюда, чтобы найти более сопоставимый тест, не понимая в то время, что concat был разрушительным. В случае, если это полезно для других, которые хотят сравнить две чисто функциональные, неразрушающие операции, здесь приведен пример добавления массива (array1 + array2) и расширения массива ([*array1, *array2]). Оба, насколько мне известно, приводят к созданию 3 создаваемых массивов: 2 массива ввода, 1 новый результирующий массив.

Подсказка: + побеждает.

код

# 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

Результат

user     system      total        real
expand  9.970000   0.170000  10.140000 ( 10.151718)
add  7.760000   0.020000   7.780000 (  7.792146)