Ruby 中数组与范围的效率

Efficiency of Arrays vs Ranges in Ruby

最近在做某事的时候,开始思考Arrays和Ranges在Ruby中的效率问题。我开始尝试研究这个,但找不到关于它的信息,甚至找不到我如何自己测试这个的信息。

所以我遇到了一些检查 HTTP 状态代码所在范围的代码,它是这样写的

SUCCESS = (200...300)
REDIRECTION = (300...400)

if SUCCESS.include?(status_code)
  status = 'success'
elsif REDIRECTION.include?(status_code)
  status = 'redirection'
end

所以这让我想到当我们基本上只需要 200...207 时使用 200...300 似乎很浪费,但是这会不会有很大的效率差异,如果有的话?

还有 4XX 代码呢,因为它并不总是范围的直线 运行,这让我想到也许我应该把它变成一个数组,这样我就可以把它写成两个方式

作为直线范围 CLIENT_ERROR = (400...429)

或作为数组 CLIENT_ERROR = [*(400...419), 422, 429]

我假设第一个选项是更好的方法并且更有效,但不太确定如何验证我的想法,因此非常感谢任何建议或意见

TL;DR

范围通常比具体化数组更快、更节省内存。但是,具体用例可能会有所不同。

如有疑问,请进行基准测试。您可以使用 irb 相对较新的 measure command, or use the Benchmark module 来比较和对比不同的方法。通常,将范围具体化为数组会占用更多内存,并且比与范围(甚至一小部分范围对象数组)进行比较要慢,但除非您多次循环此代码,否则这似乎是过早的优化。

基准

使用 Ruby 3.1.0,Range 方法在我的系统上快了大约 3,655.77%。例如:

require 'benchmark'

n = 100_000

Benchmark.bmbm do
  _1.report("Range") do
    n.times do
      client_error = [200..299, 400..499]
      client_error.include? 404
    end
  end

  _1.report("Array") do
    n.times do
      client_error = [*(200..299), *(400..499)]                                
      client_error.include? 404
    end
  end
end
Rehearsal -----------------------------------------
Range   0.022570   0.000107   0.022677 (  0.022832)
Array   0.707742   0.041499   0.749241 (  0.750012)
-------------------------------- total: 0.771918sec

            user     system      total        real
Range   0.020184   0.000043   0.020227 (  0.020245)
Array   0.701911   0.037541   0.739452 (  0.740037)

虽然使用 Jruby 和 Truffle 的总体总时间更好Ruby,但使用 Ranges 时这两种方法之间的性能差异仅快 3-7 倍。同时,Ruby 3.0.1 使用非具体化范围而不是数组显示速度提高了大约 37 倍,因此无论哪种方式,范围方法都是明显的赢家。

您的具体值将根据系统规格、系统负载以及 Ruby 版本和引擎而有所不同。对于 n 的较小值,我无法想象它会产生任何实际差异,但您绝对应该针对自己的系统进行基准测试,以确定果汁是否值得榨取。