分支预测如何影响 R 中的性能?

How does Branch Prediction affect performance in R?

部分参考资料:

这是Why is processing a sorted array faster than processing an unsorted array?

的后续

tag that I found somewhat related to branch prediction was this

中唯一的post

问题解释:

我正在调查处理排序数组是否比处理未排序数组更快(与 JavaC 中测试的问题相同 – 第一个 link)以查看是否分支预测以同样的方式影响 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 亿个元素的向量 (n=1e8) 做了 microbenchmark 并重复基准测试 100 次 ( times=100)。这是与该基准相关的图。

这是我的 sessioninfo:

R version 3.6.1 (2019-07-05)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 16299)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                           LC_TIME=English_United States.1252    

attached base packages:
[1] compiler  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] rstudioapi_0.10      reprex_0.3.0         cli_1.1.0            pkgconfig_2.0.3      evaluate_0.14        rlang_0.4.0         
[7] Rcpp_1.0.2           microbenchmark_1.4-7 ggplot2_3.2.1 

口译员的开销,而且只是成为口译员,解释了大部分平均差异。我没有更高方差的解释。


R 是一种解释型语言,不是像 Java 那样 JIT 编译成机器码,也不是像 C 那样提前编译。(我不太了解 R 的内部结构,只是CPUs 和性能,所以我在这里做了 很多 的假设。)

实际 CPU 硬件上 运行 的代码是 R 解释器,不完全是你的 R 程序。

R 程序中的控制依赖项(如 if())在解释器中变为 数据 依赖项。当前正在执行的只是解释器 运行 在真实 CPU.

上的数据

R程序中的不同操作成为解释器中的控制依赖。例如,评估 myvec[i] 然后 + 运算符可能由解释器中的两个不同函数完成。 >if() 语句的单独函数。

经典解释器循环基于 间接 分支,它从 table 函数指针调度。 而不是taken/not-taken 选择,CPU 需要对许多最近使用的目标地址之一进行预测。我不知道 R 是否使用像这样的单个间接分支,或者是否试图更有趣,例如将每个解释器块的末尾分派到下一个,而不是返回到主分派循环。

现代英特尔 CPUs(如 Haswell 及更高版本)具有 IT-TAGE(间接标记几何历史长度)预测。沿执行路径的先前分支的 taken/not-taken 状态用作 table 预测的索引。这主要解决了解释器分支预测问题,让它做得非常好,特别是当解释代码(在你的例子中是 R 代码)重复做同样的事情时。

if()被取确实导致需要做不同的操作,所以确实实际上仍然在 R 解释器中或多或少地进行一些分支 predictable 取决于数据。 但当然作为一个解释器,它正在做 很多 更多的工作在每一步都不是一个简单的机器代码循环数组。

因此,由于解释器开销,额外的分支预测错误占总时间的比例要小得多


当然,你的两个测试都是在同一个硬件上使用同一个解释器。我不知道你有什么样的CPU。

如果是比 Haswell 更早的 Intel 或比 Zen 更早的 AMD,即使使用排序数组,您也可能会得到很多错误预测,除非模式足够简单,间接分支历史预测器可以锁定。那会掩盖更多噪音的差异。

由于您确实看到了非常明显的差异,我猜测 CPU 在已排序的情况下不会错误预测太多,因此在未排序的情况下它有变得更糟的空间。