Ruby:重复选择vs AND谓词?

pre*_*non 7 ruby performance

如果我要选择一个阵列中的所有元素arr同时满足谓词p_1p_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做得更快.但是,上述任何一个选项都有所作为吗?

Kac*_*che 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
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)

现在,这重要吗?这取决于具体情况和背景的副作用。作为示例,让我们探讨几个场景:

阻塞、缓冲 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_1p_2调用都需要大量内存。也许通过交错它们,两者所需的资源必须始终可用,从而达到系统的内存限制,从而损害性能。

在这种情况下,选项 1 更快,因为一旦p_1完成所有调用,用于保存其资源的内存就可以释放以供以后的p_2调用使用。