为什么Ruby';在某些情况下,s哈希值比哈希值快?
当我将为什么Ruby';在某些情况下,s哈希值比哈希值快?,ruby,performance,internals,Ruby,Performance,Internals,当我将每个_值应用于哈希时,所需时间比使用值时长得多,即使每个_值表面上避免分配和复制数组 我写了一个简单的比较: 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
每个_值
应用于哈希时,所需时间比使用值
时长得多,即使每个_值
表面上避免分配和复制数组
我写了一个简单的比较:
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
正如预期的那样,只需调用每个方法,每个_值
就快得多,因为它只需要创建一个枚举器
,实际上并不遍历哈希表。同时,值
必须复制整个数组
然而,当我把这些值加起来时,
每一个值
方法似乎比值
方法慢3倍。为什么会这样?迭代<代码>哈希比迭代<代码>数组慢:
▶ 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_值
one实际上是迭代原始Hash
实例,而通过调用值。每次
迭代都是在数组
实例(值
)上进行的
要回答“为什么会这样”的问题,我们可能应该看看不同ruby版本的rb_array_foreach的本机实现。我认为原因是方法实现的优化 在第一个基准测试中,您将比较苹果(枚举器创建)和橙子(数组创建)。可以预料,构建整个阵列的成本要高于生成单个生成器的成本,该生成器可以访问需要额外调用的最终值 如果您编写等效的示例,结果会有所不同:
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
没什么奇怪的,只是要知道这是一个在大多数情况下都不应该依赖的特定于实现的细节。算法复杂性才是最重要的。产生每个单独值的开销?
inject(:+)
可以,并且应该比inject(&:+)
@SergioTulentsev更快。你是什么意思?这两种方法都会产生单独的值。@mudasobwa:啊,的确如此。对,但是哈希值必须在哈希上迭代才能生成数组结果,所以它的代价是生成数组(从而在哈希上迭代)加上在数组上迭代。我不确定,但是我看到散列作为一个数组存储在内存中,其键指向该数组的元素内存地址。后者不能解释为什么我们不能在需要时对每个值使用快速迭代器。我很欣赏这个问题:它非常棒,可能应该向Matz提出(比如“为什么我们不能始终使用最快的迭代器?”)Ruby肯定还有改进的空间,但答案是正确的。迭代数组比散列更快,而且显然必须获取值数组(源)不会抵消性能差异。>算法复杂性才是关键。是的,但问题的关键是Ruby内部,而不是算法设计。@bcc32是否满足了您的好奇心Hash#values
是一种专门的方法,预期其性能优于通用枚举器并随后获取其值Enumerable#inject
无法神奇地向后更改目标对象的创建方式,除非由任何Ruby实现AFAIK中都没有的复杂优化器执行。顺便说一句,在JRuby 9.0.5.0中,“求每个值的和”的例子只慢了1.1倍。对于“什么是专门的Hash#values
方法”这个问题,“Hash#values
是专门的方法”这句话回答得不太好?我看不到OP中引用的问题。specialized的意思是创建一个容器并在一个步骤/调用中存储所有结果,这可以比生成一个生成器更直接地进行优化,然后从中生成结果。