如果我想选择满足谓词p_1
和p_2
的数组arr
的所有元素,那么我有两个实现选项:
选项 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
之前会使它更快。但是,上述任何一个选项都会有所作为吗?
最佳答案
看来您已经了解谓词的性能特征和数据的形状,这太棒了!
有区别吗?简单地说,是的——评估顺序不同:
# 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
现在,这重要吗?这取决于非常情况和上下文的副作用。作为示例,让我们探讨几个场景:
阻塞、缓冲 I/O
假设 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
调用使用.
关于 ruby 性能 : Chaining selects vs AND-ing predicates?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52366705/