2

根据我之前的相关问题,我发现使用Enumerator类生成无限序列时存在巨大的性能差距。在我确实相信问题在于Enumerable方法之前takedrop但以下基准并未证实这一说法。

创建自然数生成器并以区间 (10e7-10, 10e7> 显示数字的示例:

require 'benchmark'

nats_range = (1..Float::INFINITY)
nats_enum = Enumerator.new {|y| i=1; loop { y << i; i+=1 }}

puts "#{'_'*79+"\n"}Benchmarking Enumerable methods on Range ..."
puts Benchmark.measure { print nats_range.take(10**7).drop(10**7-10), "\n" }

puts "#{'_'*79+"\n"}Benchmarking Enumerable methods on Enumerator ..."
puts Benchmark.measure { print nats_enum.take(10**7).drop(10**7-10), "\n" }


$ ruby a.rb 
_______________________________________________________________________________
Benchmarking Enumerable methods on Range ...
[9999991, 9999992, 9999993, 9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000]
  1.570000   0.010000   1.580000 (  1.576761)
_______________________________________________________________________________
Benchmarking Enumerable methods on Enumerator ...
[9999991, 9999992, 9999993, 9999994, 9999995, 9999996, 9999997, 9999998, 9999999, 10000000]
 15.620000   0.020000  15.640000 ( 15.665156)

使用 Enumerator 的等效代码要慢 10 倍!

我在这里问是否有人可以解释这种巨大的差异。我是否不正确地使用枚举器?这是当前 Ruby 实现中已知的回归吗?

核磁共振红宝石 1.9.3p385

4

2 回答 2

3

Enumerators 基于Fibers,您可以将其视为非常轻量级的线程。(实际上,它们是协程。)

Range用于succ迭代和<=确定它是否已经结束。

因此,您的示例使用了 2000 万次对and的Range方法调用,这两种方法都经过了高度优化,基本上或多或少地直接映射到对应的汇编指令。Fixnum#succFixnum#<=

您的Enumerator示例使用了 2000 万次调用Enumerator::Yielder#<<(谁知道那是多么昂贵)Fixnum#+ 以及1000 万次Fiber上下文切换。我可以很容易地想象Fiber上下文切换比简单Fixnum操作贵 10 倍。

于 2013-02-20T22:12:49.927 回答
0

我不认为你在问正确的问题。

如果您需要 10 个元素并且正在生成和存储 100 亿个元素,那么首先您的算法可能有问题。

Enumerator此外,将 a与 a进行比较是没有意义的Range,因为它Range不可能给你你所寻求的答案。

运行微基准测试很有趣,但请记住,它们通常毫无意义。无论如何,这就是我得到的更合理的限制。

class AllNumbers
  include Enumerable
  def each
    i = 0
    loop { yield i += 1 }
  end
end

custom = AllNumbers.new
enum = Enumerator.new do |y|
  i=0
  loop { y << i+=1 }
end
range = 1..Float::INFINITY
require 'fruity'

limit = 1000
compare do
  using_range        {  range.take(limit) }
  using_enumerator   {   enum.take(limit) }
  using_custom_class { custom.take(limit) }
end

结果让我有点吃惊:

using_custom_class is faster than using_range by 20.0% ± 10.0%
using_range is faster than using_enumerator by 70.0% ± 10.0%

我不会猜到的。实际上,Range#each不是针对整数进行优化的,而是针对整数+=进行优化的。

于 2013-02-20T23:07:00.087 回答