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 的较小值,我无法想象它会产生任何实际差异,但您绝对应该针对自己的系统进行基准测试,以确定果汁是否值得榨取。
最近在做某事的时候,开始思考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 的较小值,我无法想象它会产生任何实际差异,但您绝对应该针对自己的系统进行基准测试,以确定果汁是否值得榨取。