为什么这种数组构建方法如此之慢?

时间:2013-03-13 20:35:48

标签: ruby

此方法耗时7秒,有50个市场和2,500个流量(约250,000次迭代)。为什么这么慢?

def matrix
  [:origin, :destination].collect do |location|
    markets.collect do |market|
      network.flows.collect { |flow| flow[location] == market ? 1 : 0 }
    end
  end.flatten
end

我知道,根据我所运行的基准,这个市场与另一个市场的比较是缓慢的。

以下是正在比较的班级的相关部分。

module FreightFlow
  class Market
    include ActiveAttr::Model

    attribute :coordinates

    def ==(value)
      coordinates == value.coordinates
    end

  end
end

提高速度的最佳方法是什么?

3 个答案:

答案 0 :(得分:1)

您正在构建100个中间集合(2 * 50),包含总共250,000(2 * 50 * 2500)个元素,然后在最后展平它。我会尝试一次构建整个数据结构。确保marketsnetwork.flows存储在哈希或集合中。也许是这样的:

def matrix
  network.flows.collect do |flow|
    (markets.h­as_key? flow[:origin] or 
     marke­ts.has_key­? flow[:destination]) ? 1 : 0
  end
end

答案 1 :(得分:0)

这很简单,但它可以帮助...

在你最里面的循环中你正在做:

network.flows.collect { |flow| flow[location] == market ? 1 : 0 }

而不是使用三元语句转换为10,而是使用truefalse布尔值:

network.flows.collect { |flow| flow[location] == market }

这在速度上并没有太大差异,但是在很多嵌套循环的过程中它会加起来。

此外,它允许您使用生成的矩阵简化测试。您无需与10进行比较,而是可以将条件测试简化为if flow[location]if !flow[location]unless flow[location],再次加快应用程序的速度每个测试的位。如果那些是非常嵌套在循环中的,这很可能,那么一点点就可以再次加起来。

当速度很重要时,重要的事情是使用Ruby的Benchmark类来测试执行相同任务的各种方法。然后,你知道什么有效,而不是猜测。你会发现很多关于Stack Overflow的问题,我提供了一个答案,其中包含一个基准,显示了各种做事方式之间的速度差异。有时差异非常大。例如:

require 'benchmark'

puts `ruby -v`

def test1()
  true
end

def test2(p1)
  true
end

def test3(p1, p2)
  true
end

N = 10_000_000
Benchmark.bm(5) do |b|
  b.report('?:') { N.times { (1 == 1) ? 1 : 0 } }
  b.report('==') { N.times { (1 == 1) } }
  b.report('if') {
    N.times {
      if (1 == 1)
        1
      else
        0
      end
    }
  }
end

Benchmark.bm(5) do |b|
  b.report('test1') { N.times { test1() } }
  b.report('test2') { N.times { test2('foo') } }
  b.report('test3') { N.times { test3('foo', 'bar') } }
  b.report('test4') { N.times { true } }
end

结果:

ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-darwin10.8.0]
            user     system      total        real
?:      1.880000   0.000000   1.880000 (  1.878676)
==      1.780000   0.000000   1.780000 (  1.785718)
if      1.920000   0.000000   1.920000 (  1.914225)
            user     system      total        real
test1   2.760000   0.000000   2.760000 (  2.760861)
test2   4.800000   0.000000   4.800000 (  4.808184)
test3   6.920000   0.000000   6.920000 (  6.915318)
test4   1.640000   0.000000   1.640000 (  1.637506)

ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin10.8.0]
            user     system      total        real
?:      2.280000   0.000000   2.280000 (  2.285408)
==      2.090000   0.010000   2.100000 (  2.087504)
if      2.350000   0.000000   2.350000 (  2.363972)
            user     system      total        real
test1   2.900000   0.010000   2.910000 (  2.899922)
test2   7.070000   0.010000   7.080000 (  7.092513)
test3  11.010000   0.030000  11.040000 ( 11.033432)
test4   1.660000   0.000000   1.660000 (  1.667247)

有两组不同的测试。第一个是看看简单条件测试的差异与使用==而没有三元组来获得布尔区的差异。第二个是测试调用方法,使用单个参数的方法和使用两个参数的效果,而不是“内联代码”来查找调用方法时设置和拆除的成本。

现代C编译器在发出要编译的汇编语言之前分析代码时会做一些惊人的事情。我们可以对它们进行微调以写入大小或速度。当我们追求速度时,程序会随着编译器查找它可以展开的循环而增长,并将其置于“内联”代码中,以避免CPU跳转并丢弃缓存中的内容。

Ruby在语言链上的位置要高得多,但仍有一些相同的想法仍然适用。我们可以以非常干的方式编写,避免重复并使用方法和类来抽象我们的数据,但是成本降低了处理速度。答案是智能地编写代码并且不浪费CPU时间并在必要时展开/内联以获得速度,而其他时候则干燥以使代码更易于维护。

这都是一种平衡行为,而且有时间写两种方式。

答案 2 :(得分:0)

记录流量中的市场指数比任何其他解决方案都要快。当问题被提出时,时间从约30秒减少到0.6秒。

首先,我在flow_index课程中添加了Network。它存储包含市场的流量的索引。

def flow_index
  @flow_index ||= begin
    flow_index = {}
    [:origin, :destination].each do |location|
      flow_index[location] = {}
      flows.each { |flow| flow_index[location][flow[location]] = [] }
      flows.each_with_index { |flow, i| flow_index[location][flow[location]] << i }
    end
    flow_index
  end
end

然后,我重构了matrix方法以使用流索引。

def matrix
  base_row = network.flows.count.times.collect { 0 }
  [:origin, :destination].collect do |location|
    markets.collect do |market|
      row = base_row.dup
      network.flow_index[location][market].each do |i|
        row[i] = 1
      end
      row
    end
  end.flatten
end

使用全0来创建base_row,您只需在该市场的flow_index位置替换为1。