【发布时间】:2011-05-23 02:05:21
【问题描述】:
我记得曾经因为在 Python 中连接字符串而受到责骂。有人告诉我,在 Python 中创建一个字符串列表并在以后加入它们会更有效。我将这种做法带到了 JavaScript 和 Ruby 中,尽管我不确定这在后者中是否有同样的好处。
谁能告诉我加入一个字符串数组并在它们上调用 :join 或在 Ruby 编程语言中根据需要连接一个字符串是否更有效(在资源和执行方面)?
谢谢。
【问题讨论】:
标签: ruby
我记得曾经因为在 Python 中连接字符串而受到责骂。有人告诉我,在 Python 中创建一个字符串列表并在以后加入它们会更有效。我将这种做法带到了 JavaScript 和 Ruby 中,尽管我不确定这在后者中是否有同样的好处。
谁能告诉我加入一个字符串数组并在它们上调用 :join 或在 Ruby 编程语言中根据需要连接一个字符串是否更有效(在资源和执行方面)?
谢谢。
【问题讨论】:
标签: ruby
使用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)
所以在这种情况下,连接看起来要快一些。针对您的用例在您的系统上进行基准测试。
【讨论】:
ary.join 将比遍历数组并连接它们更快。
有趣的是,基准测试给出了令人惊讶的结果(除非我做错了什么):
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]
【讨论】:
是的,原理是一样的。我记得一个 ProjectEuler 谜题,我尝试了两种方式,调用 join 要快得多。
如果你查看 Ruby 源代码,join 全部用 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;
}
【讨论】:
join 方法——假设那些东西已经存在了。
我刚刚读到这个。 Attahced 是一个谈论它的链接。
据我了解,在 Python 和 Java 中,字符串是与数组不同的不可变对象,而在 Ruby 中,字符串和数组彼此一样是可变的。使用 String.concat 或
我认为该链接会比我更好地解释这一点。
谢谢,
马丁
【讨论】:
" 问题在于整个数据堆。在他的第一种情况下,他有两种类型的数据存储:(1) CSV 文件中每一行的临时字符串,带有固定的引号等内容,以及 (2) 包含所有内容的巨大字符串。如果每个字符串是 1k 并且有 5,000 行...
场景一:从小字符串构建大字符串
临时字符串:5 兆 (5,000k) 大弦:5 兆 (5,000k) 总计:10 兆(10,000k) Dave 的改进脚本将大量字符串替换为数组。他保留了临时字符串,但将它们存储在一个数组中。该数组最终只会花费 5000 * sizeof(VALUE) 而不是每个字符串的完整大小。通常,一个 VALUE 是四个字节。
场景二:将字符串存储在数组中
字符串:5 兆 (5,000k) 海量数组:20k
然后,当我们需要制作一个大字符串时,我们调用join。现在我们达到了十兆,突然所有这些字符串都变成了临时字符串,它们都可以立即释放。最终这是一个巨大的成本,但它比一个逐渐增加的、一直在消耗资源的渐强效率要高得多。 "
http://viewsourcecode.org/why/hacking/theFullyUpturnedBin.html
^实际上最好在内存/垃圾收集性能中将操作延迟到最后,就像我在 Python 中被教导的那样。原因开始于您在最后获得一大块分配和对象的即时释放。
【讨论】:
@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 比多次调用+= 更快。
【讨论】: