Ruby - Array.join против String Concatenation (Эффективность)

Я помню, как мне приходилось ругаться за конкатенирование строк в Python один раз. Мне сказали, что более эффективно создавать список строк в Python и присоединяться к ним позже. Я перевел эту практику на JavaScript и Ruby, хотя я не уверен, что это имеет ту же выгоду в последнем.

Может ли кто-нибудь сказать мне, если более эффективно (ресурс и исполнение) присоединиться к массиву строк и вызвать: присоединиться к ним или объединить строку по мере необходимости на языке программирования Ruby?

Спасибо.

Ответ 1

Попробуйте сами с классом Benchmark.

require "benchmark"

n = 1000000
Benchmark.bmbm do |x|
  x.report("concatenation") do
    foo = ""
    n.times do
      foo << "foobar"
    end
  end

  x.report("using lists") do
    foo = []
    n.times do
      foo << "foobar"
    end
    string = foo.join
  end
end

Это приводит к следующему выводу:

Rehearsal -------------------------------------------------
concatenation   0.300000   0.010000   0.310000 (  0.317457)
using lists     0.380000   0.050000   0.430000 (  0.442691)
---------------------------------------- total: 0.740000sec

                    user     system      total        real
concatenation   0.260000   0.010000   0.270000 (  0.309520)
using lists     0.310000   0.020000   0.330000 (  0.363102)

Итак, похоже, что конкатенация в этом случае немного быстрее. Тест на вашу систему для вашего прецедента.

Ответ 2

Смешно, бенчмаркинг дает неожиданные результаты (если я не делаю что-то не так):

require 'benchmark'

N = 1_000_000
Benchmark.bm(20) do |rep|

  rep.report('+') do
    N.times do
      res = 'foo' + 'bar' + 'baz'
    end
  end

  rep.report('join') do
    N.times do
      res = ['foo', 'bar', 'baz'].join
    end
  end

  rep.report('<<') do
    N.times do
      res = 'foo' << 'bar' << 'baz'
    end
  end
end

дает

[email protected]:~/dev/rb$ ruby concat.rb 
                          user     system      total        real
+                     1.760000   0.000000   1.760000 (  1.791334)
join                  2.410000   0.000000   2.410000 (  2.412974)
<<                    1.380000   0.000000   1.380000 (  1.376663)

join оказывается самым медленным. Возможно, это связано с созданием массива, но тем, что вам все равно придется делать.

О, BTW,

[email protected]:~/dev/rb$ ruby -v
ruby 1.9.1p378 (2010-01-10 revision 26273) [i486-linux]

Ответ 3

Да, это тот же принцип. Я помню загадку ProjectEuler, в которой я пробовал это в обоих направлениях, вызов соединения происходит намного быстрее.

Если вы посмотрите источник Ruby, соединение будет реализовано на C, это будет намного быстрее, чем конкатенация строк (без создания промежуточного объекта, без сбора мусора):

/*
 *  call-seq:
 *     array.join(sep=$,)    -> str
 *  
 *  Returns a string created by converting each element of the array to
 *  a string, separated by <i>sep</i>.
 *     
 *     [ "a", "b", "c" ].join        #=> "abc"
 *     [ "a", "b", "c" ].join("-")   #=> "a-b-c"
 */

static VALUE
rb_ary_join_m(argc, argv, ary)
    int argc;
    VALUE *argv;
    VALUE ary;
{
    VALUE sep;

    rb_scan_args(argc, argv, "01", &sep);
    if (NIL_P(sep)) sep = rb_output_fs;

    return rb_ary_join(ary, sep);
}

где rb_ary_join:

 VALUE rb_ary_join(ary, sep)
     VALUE ary, sep;
 {
     long len = 1, i;
     int taint = Qfalse;
     VALUE result, tmp;

     if (RARRAY(ary)->len == 0) return rb_str_new(0, 0);
     if (OBJ_TAINTED(ary) || OBJ_TAINTED(sep)) taint = Qtrue;

     for (i=0; i<RARRAY(ary)->len; i++) {
     tmp = rb_check_string_type(RARRAY(ary)->ptr[i]);
     len += NIL_P(tmp) ? 10 : RSTRING(tmp)->len;
     }
     if (!NIL_P(sep)) {
     StringValue(sep);
     len += RSTRING(sep)->len * (RARRAY(ary)->len - 1);
     }
     result = rb_str_buf_new(len);
     for (i=0; i<RARRAY(ary)->len; i++) {
     tmp = RARRAY(ary)->ptr[i];
     switch (TYPE(tmp)) {
       case T_STRING:
         break;
       case T_ARRAY:
         if (tmp == ary || rb_inspecting_p(tmp)) {
         tmp = rb_str_new2("[...]");
         }
         else {
         VALUE args[2];

         args[0] = tmp;
         args[1] = sep;
         tmp = rb_protect_inspect(inspect_join, ary, (VALUE)args);
         }
         break;
       default:
         tmp = rb_obj_as_string(tmp);
     }
     if (i > 0 && !NIL_P(sep))
         rb_str_buf_append(result, sep);
     rb_str_buf_append(result, tmp);
     if (OBJ_TAINTED(tmp)) taint = Qtrue;
     }

     if (taint) OBJ_TAINT(result);
     return result;
}

Ответ 4

Я просто читал об этом. Attahced - это ссылка, говорящая об этом.

Building-a-String-from-Parts

Из того, что я понимаю, в Python и Java строки являются неизменяемыми объектами, в отличие от массивов, тогда как в Ruby обе строки и массивы изменяются как друг друга. Может быть минимальная разница в скорости между использованием String.concat или < метод для создания строки в сравнении с Array.join, но это не кажется большой проблемой.

Я думаю, что ссылка объяснит это намного лучше, чем я.

Спасибо,

Martin

Ответ 5

" Проблема заключается в куче данных в целом. В своей первой ситуации у него было два типа накопления данных: (1) временная строка для каждой строки в его файле CSV, с фиксированными цитатами и такими вещами, и (2) гигантская строка, содержащая все. Если каждая строка равна 1k и существует 5000 строк...

Сценарий Первый: постройте большую строку из маленьких строк

временные строки: 5 мегабайт (5 000 тыс.) массивная строка: 5 мегабайт (5 000 тыс.) ИТОГО: 10 мегабайт (10 000 тыс.) Дэйв улучшил script, заменил массивную строку для массива. Он хранил временные строки, но хранил их в массиве. Массив будет стоить 5000 * sizeof (VALUE), а не полный размер каждой строки. И вообще, VALUE - это четыре байта.

Сценарий 2. Сохранение строк в массиве

строки: 5 мегабайт (5 000 тыс.) массив массив: 20k

Затем, когда нам нужно создать большую строку, мы вызываем join. Теперь мы до десяти мегабайт, и внезапно все эти строки становятся временными строками, и все они могут быть немедленно выпущены. Это огромная стоимость в конце, но она намного эффективнее, чем постепенное крещендо, которое все время ест ресурсы. "

http://viewsourcecode.org/why/hacking/theFullyUpturnedBin.html

^ На самом деле лучше использовать для работы с памятью/сборкой мусора, чтобы отложить операцию до конца, точно так же, как меня учили в Python. Причина начинается с того, что вы получаете один огромный кусок выделения в конце и мгновенный выпуск объектов.