Ruby字符串搜索:使用split还是正则表达式更快?

9

这是一个双重问题。假设你有一个字符串数组,可以在一个字符处分割(例如,在@符号或文件名中的.),最有效的方法是找到分割字符前面的字符?

my_string.split(char)[0]

或者

my_string[/regex/]

问题的第二部分是如何编写正则表达式以获取第一个字符之前的所有内容。下面的正则表达式可以找到在 '.' 之前的特定字符(因为 '.' 不在模式中),但这是我想到的一种解决方法。

my_string[/[A-Za-z0-9\_-]+/]

感谢您的信任!

6
处理电子邮件地址时,除非每秒处理数百万个电子邮件地址,否则不太可能有明显差异。但是,为什么不自己测量一下两者的差别呢? - Bart Kiers
2个回答

14

回答第一个问题最简单的方法是,像往常一样,用您的实际数据进行基准测试。例如:

require 'benchmark'
Benchmark.bm do |x|
  x.report { 50000.times { a = 'a@b.c'.split('@')[0] } }
  x.report { 50000.times { a = 'a@b.c'[/[^@]+/] } }
end

我的设置上说:

      user     system      total        real
  0.130000   0.010000   0.140000 (  0.130946)
  0.090000   0.000000   0.090000 (  0.096260)
所以正则表达式的解决方案看起来稍微快一些,但即使进行 50,000 次迭代,差别也几乎不可察觉。另一方面,正则表达式的解决方案精确地表达了您的意思(“给我第一个@之前的所有内容”),而split解决方案则通过稍微绕弯路来获得您想要的结果。

Split方法可能会更慢,因为它需要扫描整个字符串将其分割成片段,然后构建一个片段数组,并最终提取数组的第一个元素并且放弃其余部分;我不知道虚拟机是否聪明到足以认识到它不需要构建数组,所以这只是一些快速的猜测。

就您的第二个问题而言,说清楚您的意思:

my_string[/[^.]+/]

如果你想要第一个句号之前的所有内容,那么应该说“直到句号为止的所有内容”,而不是“第一个由这些字符组成的块(这些字符恰好不包含句号)”。


谢谢你,我不知道 Ruby 内置了基准测试工具。在我的测试中,我发现正则表达式更快,只要第一个子字符串小于 50 个字符,之后分割就更快了。当然,正如提到的那样,对于小数据集,你几乎察觉不到差异。 - kreek
1
@Kreek:这就是你在SO上混的原因,学习新东西 :) 我认为最好的方法是尽可能清晰地用代码表达你的意图,如果真的存在问题再考虑性能。 - mu is too short
@muistooshort,难道没有缓存效应或页面错误吗? - Benjamin
@Benjamin:可能会反复做同样的事情。另一方面,不要忘记我们已经优化正则表达式引擎几十年了,所以它们可以非常快。我的哲学是尽可能清晰地表达你的意思,然后如果有问题再寻找加速方法。 - mu is too short

5

partitionsplit 更快,因为它在第一个匹配后就停止检查。

使用带有 index 的常规 slice 比正则表达式 slice 更快。

当匹配前字符串的部分变得更大时,正则表达式切片也会明显减慢。在 ~ 10 个字符后它比原始分割更慢,然后从那时起变得更糟。如果您有一个没有 +* 匹配的正则表达式,我认为它会好一些。

require 'benchmark'
n=1000000

def bench n,email
  printf "\n%s %s times\n", email, n
  Benchmark.bm do |x|
      x.report('split    ') do n.times{ email.split('@')[0]  } end
      x.report('partition') do n.times{ email.partition('@').first  } end
      x.report('slice reg') do n.times{ email[/[^@]+/]  } end
      x.report('slice ind') do n.times{ email[0,email.index('@')]  } end
  end
end


bench n, 'a@be.pl'
bench n, 'some_name@regulardomain.com'
bench n, 'some_really_long_long_email_name@regulardomain.com'
bench n, 'some_name@rediculously-extra-long-silly-domain.com'
bench n, 'some_really_long_long_email_name@rediculously-extra-long-silly-domain.com'
bench n, 'a'*254 + '@' + 'b'*253    # rfc limits
bench n, 'a'*1000 + '@' + 'b'*1000  # for other string processing

结果 1.9.3p484:

a@be.pl 1000000 times
       user     system      total        real
split      0.405000   0.000000   0.405000 (  0.410023)
partition  0.375000   0.000000   0.375000 (  0.368021)
slice reg  0.359000   0.000000   0.359000 (  0.357020)
slice ind  0.312000   0.000000   0.312000 (  0.309018)

some_name@regulardomain.com 1000000 times
       user     system      total        real
split      0.421000   0.000000   0.421000 (  0.432025)
partition  0.374000   0.000000   0.374000 (  0.379021)
slice reg  0.421000   0.000000   0.421000 (  0.411024)
slice ind  0.312000   0.000000   0.312000 (  0.315018)

some_really_long_long_email_name@regulardomain.com 1000000 times
       user     system      total        real
split      0.593000   0.000000   0.593000 (  0.589034)
partition  0.531000   0.000000   0.531000 (  0.529030)
slice reg  0.764000   0.000000   0.764000 (  0.771044)
slice ind  0.484000   0.000000   0.484000 (  0.478027)

some_name@rediculously-extra-long-silly-domain.com 1000000 times
       user     system      total        real
split      0.483000   0.000000   0.483000 (  0.481028)
partition  0.390000   0.016000   0.406000 (  0.404023)
slice reg  0.406000   0.000000   0.406000 (  0.411024)
slice ind  0.312000   0.000000   0.312000 (  0.344020)

some_really_long_long_email_name@rediculously-extra-long-silly-domain.com 1000000 times
       user     system      total        real
split      0.639000   0.000000   0.639000 (  0.646037)
partition  0.609000   0.000000   0.609000 (  0.596034)
slice reg  0.764000   0.000000   0.764000 (  0.773044)
slice ind  0.499000   0.000000   0.499000 (  0.491028)

a<254>@b<253> 1000000 times
       user     system      total        real
split      0.952000   0.000000   0.952000 (  0.960055)
partition  0.733000   0.000000   0.733000 (  0.731042)
slice reg  3.432000   0.000000   3.432000 (  3.429196)
slice ind  0.624000   0.000000   0.624000 (  0.625036)

a<1000>@b<1000> 1000000 times
       user     system      total        real
split      1.888000   0.000000   1.888000 (  1.892108)
partition  1.170000   0.016000   1.186000 (  1.188068)
slice reg 12.885000   0.000000  12.885000 ( 12.914739)
slice ind  1.108000   0.000000   1.108000 (  1.097063)

2.1.3p242的差异大约相同,但在所有方面都比它快10-30%,除了正则表达式拆分之外,它会更慢。


从技术上讲,一个电子邮件地址可以包含多个@符号。然而,在现实世界中,这种情况非常罕见,很可能是因为主要的提供商和工具不允许出现这种情况。但是要记住...因为当出现错误时,即使是微小的性能提升也会付之东流 :) - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接