当我将each_value
应用于哈希时,它需要比使用values
时更长的时间,即使each_value
表面上避免分配和复制数组。
我写了一个简单的比较:
require 'benchmark/ips'
some_hash = File.open('with_an.dat') { |f| Marshal.load f }
Benchmark.ips do |x|
x.report "calling each_value" do
some_hash.each_value
end
x.report "calling values" do
some_hash.values
end
x.compare!
end
Benchmark.ips do |x|
x.report "summing each_value" do
some_hash.each_value.inject &:+
end
x.report "summing values" do
some_hash.values.inject &:+
end
x.compare!
end
以下是结果:
Calculating -------------------------------------
calling each_value 58.166k i/100ms
calling values 2.000 i/100ms
-------------------------------------------------
calling each_value 1.312M (±40.7%) i/s - 5.468M
calling values 29.423 (±10.2%) i/s - 146.000
Comparison:
calling each_value: 1312156.6 i/s
calling values: 29.4 i/s - 44596.28x slower
Calculating -------------------------------------
summing each_value 1.000 i/100ms
summing values 1.000 i/100ms
-------------------------------------------------
summing each_value 2.107 (± 0.0%) i/s - 11.000
summing values 8.002 (±12.5%) i/s - 40.000
Comparison:
summing values: 8.0 i/s
summing each_value: 2.1 i/s - 3.80x slower
正如预期的那样,只调用每个方法each_value
要快得多,因为它只需要创建一个Enumerator
,并且实际上并不遍历哈希表。同时,values
必须复制整个数组。
然而,当我添加值时,似乎each_value
方法比values
方法慢3倍 。为什么会这样?
答案 0 :(得分:1)
迭代Hash
比迭代Array
更慢:
▶ Benchmark.bm do |x|
▷ x.report do
▷ n.times do
▷ {a: 1, b: 2, c: 3, d: 4, e: 5}.inject(1) { |memo, (_, v)| memo * v }
▷ end
▷ end
▷ x.report do
▷ n.times do
▷ [1, 2, 3, 4, 5].inject(1) { |memo, v| memo * v }
▷ end
▷ end
▷ end
#⇒ user system total real
#⇒ 0.700000 0.010000 0.710000 ( 0.712821)
#⇒ 0.340000 0.000000 0.340000 ( 0.349040)
通过调用each_value
实际上迭代原始Hash
实例,而通过调用values.each
,迭代正在Array
实例上完成(values
。)
要回答“为什么会这样”的问题,我们应该查看不同ruby版本的rb_hash_foreach
和rb_array_foreach
本机实现。
答案 1 :(得分:0)
我说原因是Hash#values方法实施的优化。
在你的第一个基准测试中,你将苹果(枚举器创建)与橙子(数组创建)进行比较。可以预料的是,构建整个阵列的成本更高,然后生成一个可以访问需要额外调用的最终值的单个生成器。
如果您编写等效示例,结果会有所不同:
some_hash = ('aa'..'zz').each_with_index.to_h
Benchmark.ips do |x|
x.report "array from map" do
some_hash.map &:last
end
x.report "array from each_value" do
some_hash.each_value.to_a
end
x.report "array from values" do
some_hash.values
end
x.compare!
end
Comparison:
array from values: 171143.8 i/s
array from each_value: 15195.8 i/s - 11.26x slower
array from map: 6040.9 i/s - 28.33x slower
没有什么太令人惊讶的,请注意这是一个在大多数情况下不应该依赖的实现特定细节。算法的复杂性非常重要。