Ruby - Array.join vs String Concatenation(效率)

时间:2010-12-09 19:38:36

标签: ruby

我记得曾经一度骂过在Python中连接字符串。有人告诉我,在Python中创建一个字符串列表并在以后加入它们会更有效。我把这种做法带到了JavaScript和Ruby中,虽然我不确定它在后者中是否具有相同的好处。

任何人都可以告诉我,加入一个字符串数组并调用它是否更有效(资源和执行):加入它们或者根据需要在Ruby编程语言中连接一个字符串?

感谢。

6 个答案:

答案 0 :(得分:30)

使用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)

因此,在这种情况下,连接似乎更快一些。根据您的用例对您的系统进行基准测试。

答案 1 :(得分:8)

有趣,基准测试给出了令人惊讶的结果(除非我做错了):

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

给出

jablan@poneti:~/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原来是最慢的。它可能与创建数组有关,但这就是你必须要做的事情。

哦,顺便说一句,

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

答案 2 :(得分:4)

是的,这是相同的原则。我记得一个ProjectEuler拼图,我在这两个方面都尝试过,调用join更快。

如果查看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;
}

答案 3 :(得分:3)

我刚刚读到这个。 Attahced是一个谈论它的链接。

Building-a-String-from-Parts

据我所知,在Python和Java字符串中,不可变对象与数组不同,而在Ruby中,字符串和数组彼此可变。使用String.concat或&lt;&lt;之间的速度差异可能很小。形成字符串与Array.join的方法,但它似乎不是一个大问题。

我认为链接会比我更好地解释这个。

谢谢,

马丁

答案 4 :(得分:0)

” 问题是整个数据堆。在他的第一种情况下,他有两种类型的数据存储:(1)他的CSV文件中每行的临时字符串,固定引号和类似的东西,以及(2)包含所有内容的巨型字符串。如果每个字符串是1k并且有5,000行...

场景一:用小字符串构建一个大字符串

临时字符串:5兆(5,000k) 巨大的字符串:5兆(5,000k) 总计:10兆(10,000k) 戴夫的改进脚本将大量字符串替换为数组。他保留了临时字符串,但将它们存储在一个数组中。该数组最终只会花费5000 * sizeof(VALUE)而不是每个字符串的完整大小。通常,VALUE是四个字节。

场景二:在数组中存储字符串

字符串:5兆(5,000k) 大规模阵列:20k

然后,当我们需要制作一个大字符串时,我们称之为join。现在我们达到了10兆,突然所有这些字符串变成了临时字符串,它们都可以立即被释放。最终这是一个巨大的成本,但它比一直消耗资源的渐进式渐强效率要高得多。 “

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

^在内存/垃圾收集性能方面实际上更好的是将操作延迟到最后,就像我在Python中教过的那样。之所以开始,你会在最后获得一大块分配并立即释放对象。

答案 5 :(得分:0)

@jergason 的 answer 显示连接速度稍快,但这是 because the shovel operator << is allowed to modify the original string

如果我们使用 frozen_string_literal: true 在顶部运行相同的基准测试,您会得到以下结果:

Rehearsal -------------------------------------------------
using lists     0.140621   0.015146   0.155767 (  0.308191)
concatenation Traceback (most recent call last):
    8: from main.rb:5:in `<main>'
    7: from /usr/lib/ruby/2.5.0/benchmark.rb:255:in `bmbm'
    6: from /usr/lib/ruby/2.5.0/benchmark.rb:255:in `inject'
    5: from /usr/lib/ruby/2.5.0/benchmark.rb:255:in `each'
    4: from /usr/lib/ruby/2.5.0/benchmark.rb:257:in `block in bmbm'
    3: from /usr/lib/ruby/2.5.0/benchmark.rb:293:in `measure'
    2: from main.rb:16:in `block (2 levels) in <main>'
    1: from main.rb:16:in `times'
main.rb:17:in `block (3 levels) in <main>': can't modify frozen String (FrozenError)

如果您更新串联基准以使用 += 而不是 <<,您会发现串联基准永远不会终止。

因此,Array#join 比多次调用 += 快。