一些参考文献:
这是此Why is processing a sorted array faster than processing an unsorted array?
的后续内容r标签中唯一与分支预测有些相关的帖子是此Why sampling matrix row is very slow?
问题解释:
我正在调查处理排序数组是否比处理未排序数组快(与Java
和C
中测试的问题相同-第一个链接),以查看分支预测是否影响{{ 1}}以相同的方式。
查看下面的基准示例:
R
set.seed(128)
#or making a vector with 1e7
myvec <- rnorm(1e8, 128, 128)
myvecsorted <- sort(myvec)
mysumU = 0
mysumS = 0
SvU <- microbenchmark::microbenchmark(
Unsorted = for (i in 1:length(myvec)) {
if (myvec[i] > 128) {
mysumU = mysumU + myvec[i]
}
} ,
Sorted = for (i in 1:length(myvecsorted)) {
if (myvecsorted[i] > 128) {
mysumS = mysumS + myvecsorted[i]
}
} ,
times = 10)
ggplot2::autoplot(SvU)
所示吗?N.B。 。我的CPU是 i7-6820HQ @ 2.70GHz Skylake,具有超线程的四核。
要研究变异部分,我用1亿个元素的向量(Java
进行了microbenchmark
并重复了100次基准测试(n=1e8
)。这是该基准的关联图。
这是我的times=100
:
sessioninfo
答案 0 :(得分:1)
解释器的开销以及成为的解释器,可以解释大部分平均差异。对于较高的差异,我没有任何解释。
R是一种解释型语言,不是JIT编译为Java之类的机器代码,也不是像C这样的提前编译的机器。(我对R的内部知识了解不多,只有CPU和性能方面,在此处进行很多的假设。)
在实际CPU硬件上运行的代码是 R解释器,而不完全是您的R程序。
R程序中的控件依赖项(如if()
)在解释器中成为 data 依赖项。当前正在执行的只是在真实CPU上运行的解释器的数据。
R程序中的不同操作成为解释器中的控件依赖项。例如,对myvec[i]
进行评估,然后+
运算符很可能由解释器中的两个不同函数完成。还有>
和if()
语句的单独函数。
经典的解释器循环基于从函数指针表中分派的间接分支。 CPU需要一个对许多最近使用的目标地址之一的预测。我不知道R是否使用像这样的单个间接分支,或者是否试图像将每个解释器块的末尾分派到下一个分支一样,而不是返回到主分派循环,从而变得更加幻想。
现代Intel CPU(例如Haswell和更高版本)具有IT-TAGE(间接捕获的几何历史记录长度)预测。沿执行路径的先前分支的已采用/未采用状态用作预测表的索引。这主要解决了解释器分支预测问题,使它可以做的出奇的出色,尤其是当解释的代码(在您的情况下为R代码)重复执行相同的操作时。
使用if()
确实需要执行不同的操作,因此实际上仍然在R解释器中进行了一些分支当然,作为解释器,与在数组上进行简单的机器代码循环相比,它在每个步骤上要做的工作要多得多。
由于解释器的开销,额外的分支错误预测占总时间的比例要小得多。
当然,您的两个测试都在相同的硬件上使用相同的解释器。我不知道您拥有哪种CPU。
如果它的Intel早于Haswell或AMD早于Zen,那么即使使用排序数组,也可能会出现很多错误的预测,除非该模式足够简单以使间接分支历史预测器可以锁定。这样可以将差异掩盖在更多的噪音中。
由于您确实看到了非常明显的差异,所以我猜测在排序情况下CPU不会预测太多,因此在未排序情况下它有变差的空间。