如果我要选择一个阵列中的所有元素arr
同时满足谓词p_1
和p_2
,然后我有两个选项实现:
选项1:
arr.select{|x| x.p_1}.select{|x| x.p_2}
Run Code Online (Sandbox Code Playgroud)
选项2:
arr.select{|x| x.p_1 && x.p_2}
Run Code Online (Sandbox Code Playgroud)
这两者之间有显着差异吗?在我的用例中,谓词p_1
减少了列表p_2
,并且比列表p_2
更昂贵p_1
.因此,我怀疑p_1
之前要p_2
做得更快.但是,上述任何一个选项都有所作为吗?
看起来您已经了解谓词的性能特征和数据的形状,这太棒了!
有区别吗?简而言之,是的——评估顺序不同:
# 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
Run Code Online (Sandbox Code Playgroud)
相对
# 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
Run Code Online (Sandbox Code Playgroud)
现在,这重要吗?这取决于具体情况和背景的副作用。作为示例,让我们探讨几个场景:
假设成本要高得多的原因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
调用使用。