如果我想选择同时满足谓词arr
和p_1
的数组p_2
的所有元素,那么我有两种实现方式:
选项1:
arr.select{|x| x.p_1}.select{|x| x.p_2}
选项2:
arr.select{|x| x.p_1 && x.p_2}
两者之间有显着差异吗?在我的用例中,谓词p_1
比p_2
所减少的列表要多得多,而p_2
比p_1
要昂贵。因此,我怀疑将p_1
放在p_2
之前会更快。但是上面的两个选项都有区别吗?
答案 0 :(得分:0)
您似乎已经知道谓词的性能特征和数据的形状,这很棒!
有区别吗?简而言之,是的-评估顺序不同:
# Option 1
arr[0].p_1
arr[1].p_1
arr[2].p_1
...
arr[n].p_1
arr[0].p_2
arr[1].p_2
arr[2].p_2
...
arr[n].p_2
与
# Option 2
arr[0].p_1
arr[0].p_2
arr[1].p_1
arr[1].p_2
arr[2].p_1
arr[2].p_2
...
arr[n].p_1
arr[n].p_2
现在,重要吗?这取决于非常情况和上下文的副作用。作为示例,让我们探索一些场景:
假设p_2
昂贵得多的原因是因为它执行某些I / O操作,例如写入磁盘。可能是这种输出操作被缓冲的情况,虽然Ruby运行时可能从p_2
调用返回,但是直到再次调用p_2
时,输出仍在刷新,从而阻塞了它。>
在这种特殊情况下,选项2更快,因为p_1
的计算可以在相互阻塞的p_2
调用之间的过渡期间继续进行。
假设p_1
之所以快是因为可以缓存其计算。我们还假设调用p_2
会以某种方式破坏高速缓存,使得随后的p_1
调用会丢失高速缓存:
p_1
花费的时间太长,在p_2
次调用之间清除了缓存的数据在这种情况下,选项1更快,因为分组的p_1
调用能够利用缓存。
假设p_1
和p_2
调用都需要大量内存。也许通过交错它们,必须使两者所需的资源始终可用,从而达到系统的内存限制,从而损害性能。
在这种情况下,选项1更快,因为一旦完成所有p_1
调用,就可以释放用于保留其资源的内存,以供以后的p_2
调用使用。
答案 1 :(得分:-1)
根据您所说的,我已经建立了基准:
require 'benchmark'
N = 1000
# the fast method
def p1(arr_param)
# lazy init of the arr_param, so it returns 20 times true and 80 times false
(arr_param << Array.new(20, true) << Array.new(80, false)).flatten! if arr_param.empty?
# shorter sleep
t = Time.now.to_f
while true
break if Time.now.to_f - t > 0.000_01
end
arr_param.shift
end
# the slow method
def p2
# longer sleep
t = Time.now.to_f
while true
break if Time.now.to_f - t > 0.001
end
true
end
# testing arrays
arr = (1..100).to_a
truth_arr = []
Benchmark.bm(7) do |b|
b.report('chain') { N.times { arr.select { |_| p1(truth_arr) }.select { |_| p2 } } }
b.report('and') { N.times { arr.select { |_| p1(truth_arr) && p2 } } }
end
结果是:
#=> user system total real
#=> chain 78.422000 0.000000 78.422000 ( 78.789006)
#=> and 78.375000 0.000000 78.375000 ( 79.313160)
因此,似乎两种方法都一样快。不过,比我更有知识的人将不得不解释为什么。