此方法耗时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
提高速度的最佳方法是什么?
答案 0 :(得分:1)
您正在构建100个中间集合(2 * 50),包含总共250,000(2 * 50 * 2500)个元素,然后在最后展平它。我会尝试一次构建整个数据结构。确保markets
和network.flows
存储在哈希或集合中。也许是这样的:
def matrix
network.flows.collect do |flow|
(markets.has_key? flow[:origin] or
markets.has_key? flow[:destination]) ? 1 : 0
end
end
答案 1 :(得分:0)
这很简单,但它可以帮助...
在你最里面的循环中你正在做:
network.flows.collect { |flow| flow[location] == market ? 1 : 0 }
而不是使用三元语句转换为1
或0
,而是使用true
和false
布尔值:
network.flows.collect { |flow| flow[location] == market }
这在速度上并没有太大差异,但是在很多嵌套循环的过程中它会加起来。
此外,它允许您使用生成的矩阵简化测试。您无需与1
或0
进行比较,而是可以将条件测试简化为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。