为什么是String#split(" ") 和 Array#join(' ') 比 String#gsub(/ /, ' ')?

问题描述 投票:0回答:3

我必须从大量字符串中删除所有换行符。在对

string.join("\n").split(' ')
string.gsub(/\n/, ' ')
进行基准测试时,我发现 split 和 join 方法要快得多,但很难理解为什么。我不明白如何在每次遇到
\n
时将字符串拆分为数组元素,然后将数组连接到新字符串可能比扫描并用
\n
替换每个
' '
更快。

sentence = %q[
  Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
  totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
  dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
  sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
  est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius
  modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
  veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea]

检查以验证两种方法的输出确实相同:

puts sentence.split("\n").join(' ') == sentence.gsub(/\n/, ' ')
#=> true

用于基准测试的脚本:

def split_join_method(string)
  start = Time.now;
  1000000.times {  string.split("\n").join(' ') }
  puts "split_join: #{Time.now - start} s"
end

def gsub_method(string)
  start = Time.now;
  1000000.times {  string.gsub(/\n/, ' ') }
  puts "gsub: #{Time.now - start} s"
end

5.times do
  split_join_method(sentence)
  gsub_method(sentence)
end

结果:

#=> split_join: 6.753057 s
#=> gsub: 14.938358 s
#=> split_join: 6.16101 s
#=> gsub: 14.166971 s
#=> split_join: 5.946168 s
#=> gsub: 13.490355 s
#=> split_join: 5.781062 s
#=> gsub: 13.436135 s
#=> split_join: 5.903052 s
#=> gsub: 15.670774 s
ruby regex string
3个回答
3
投票

您的问题是比较苹果和橙子,因为您将正则表达式方法与字符串搜索操作进行比较。

我的基准测试无法重现您的观察结果,即

split
join
组合通常比简单的
gsub
更快,
gsub
版本总是更快。我唯一可以确认的是,正则表达式匹配比字符串搜索慢,这并不奇怪。

顺便说一句。

tr
是此类问题最快的解决方案:

Rehearsal ---------------------------------------------------
string_split:     5.390000   0.100000   5.490000 (  5.480459)
regexp_split:    14.220000   0.160000  14.380000 ( 14.413509)
string_gsub :     3.750000   0.090000   3.840000 (  3.832316)
regexp_gsub :    12.890000   0.130000  13.020000 ( 13.045899)
string_tr   :     2.480000   0.050000   2.530000 (  2.525891)
----------------------------------------- total: 39.260000sec

                      user     system      total        real
string_split:     5.450000   0.090000   5.540000 (  5.543735)
regexp_split:    14.340000   0.190000  14.530000 ( 14.552214)
string_gsub :     4.160000   0.120000   4.280000 (  4.543941)
regexp_gsub :    13.570000   0.200000  13.770000 ( 14.356955)
string_tr   :     2.390000   0.040000   2.430000 (  2.431676)

我用于此基准测试的代码:

require 'benchmark'

@string = %q[
  Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
  totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
  dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
  sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
  est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius
  modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
  veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea]

def string_split
  @string.split("\n").join(' ')
end

def regexp_split
  @string.split(/\n/).join(' ')
end

def string_gsub
  @string.gsub("\n", ' ')
end

def regexp_gsub
  @string.gsub(/\n/, ' ')
end

def string_tr
  @string.tr("\n", ' ')
end

n = 1_000_000
Benchmark.bmbm(15) do |x|
  x.report("string_split:")   { n.times do; string_split; end }
  x.report("regexp_split:")   { n.times do; regexp_split; end }
  x.report("string_gsub :")   { n.times do; string_gsub ; end }
  x.report("regexp_gsub :")   { n.times do; regexp_gsub ; end }
  x.report("string_tr   :")   { n.times do; string_tr   ; end }
end

2
投票

我认为

gsub
需要更多时间,原因有两个:

首先,使用正则表达式引擎有初始成本,至少要解析模式。

第二个,可能也是最重要的一点是,正则表达式引擎逐个字符地进行哑走操作,并在分割(此处使用文字字符串)使用快速字符串搜索算法(可能是)时测试字符串中每个位置的模式Boyer-Moore 算法)。

请注意,即使拆分/连接方式更快,它也可能使用更多内存,因为这种方式需要生成新字符串。

注2:一些正则表达式引擎能够在步行之前使用这种快速字符串搜索算法来查找位置,但我没有有关 ruby 正则表达式引擎的信息。

注3:更好地了解包含很少重复但具有较大字符串的测试会发生什么可能会很有趣。 [编辑] 经过对 @spickermann 代码的多次测试,即使重复很少,它似乎也没有改变任何东西(或者没有什么非常重要的东西)。所以最初的成本可能不是那么重要。


1
投票

这是因为在您的

gsub
代码中,您使用的是正则表达式,由于卡西米尔的答案中指出的原因,正则表达式很慢。这是证据:如果你改变

string.gsub(/\n/, ' ')

string.gsub("\n", ' ')

那么 gsub 代码实际上比 split/join 代码更快

© www.soinside.com 2019 - 2024. All rights reserved.