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. Причина начинается с того, что вы получаете один огромный кусок выделения в конце и мгновенный выпуск объектов.