【问题标题】:How does Branch Prediction affect performance in R?分支预测如何影响 R 中的性能?
【发布时间】:2020-02-12 09:55:22
【问题描述】:

一些参考资料:

这是Why is processing a sorted array faster than processing an unsorted array?的后续跟进

我发现标签中唯一与分支预测有点相关的帖子是Why sampling matrix row is very slow?

问题说明:

我正在调查处理排序数组是否比处理未排序数组更快(与JavaC - 第一个链接中测试的问题相同)以查看分支预测是否以相同的方式影响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)

问题:

  • 首先,我想知道为什么 "Sorted" 向量不是一直最快的,也不是像Java 中表达的那样大?
  • 其次,为什么排序后的执行时间与未排序的执行时间相比变化更大?

注意我的 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 r performance benchmarking interpreter branch-prediction


【解决方案1】:

解释器开销,并且只是作为解释器,解释了大部分平均差异。我没有解释更高的方差。


R 是一种解释型语言,不是 JIT 编译为 Java 之类的机器代码,也不是 C 之类的提前编译语言。(我不太了解 R 的内部结构,只知道 CPU 和性能,所以我在这里做了很多个假设。)

在实际 CPU 硬件上运行的代码是R 解释器,而不是您的 R 程序。

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

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

经典的解释器循环基于一个间接分支,该分支从一个函数指针表中分派。 CPU 需要一个取/不取的选择,而不是一个采取/不采取的选择。预测许多最近使用的目标地址之一。我不知道 R 是否使用像这样的单个间接分支,或者是否试图变得更花哨,比如让每个解释器块的末尾分派到下一个,而不是返回到主分派循环。

现代英特尔 CPU(如 Haswell 及更高版本)具有 IT-TAGE(间接标记几何历史长度)预测。沿执行路径的先前分支的采用/未采用状态用作预测表的索引。这主要解决了解释器分支预测问题,使其能够出色地完成工作,尤其是当解释代码(在您的情况下为 R 代码)重复执行相同操作时。

if() 被采用确实导致需要执行不同的操作,因此它确实实际上仍然或多或少地在 R 解释器中进行了一些分支可根据数据进行预测。但当然,作为解释器,它在每个步骤中所做的工作更多,而不是在数组上进行简单的机器代码循环。

因此,由于解释器开销,额外的分支错误预测占总时间的一小部分


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

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

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

【讨论】:

  • 谢谢彼得。我需要花一些时间详细阅读您的答案,因为它写得很好,很全面。但只是为了解决您对我的 CPU 的疑惑,以下是信息:Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz, 2701 Mhz, 4 Core(s), 8 Logical Processor(s)p.s。如果您基于此更新您的答案,请保留现有答案,因为它解决了一般问题,而不是专门针对我的配置。
  • @M--:好吧,那是 Skylake,Haswell 之后的一代。所以是的,你有 ITTAGE 分支预测,就像我猜的那样。
猜你喜欢
  • 2020-07-16
  • 2023-03-12
  • 1970-01-01
  • 2016-09-14
  • 2012-08-28
  • 2019-08-20
  • 2016-03-11
  • 2018-10-02
  • 2018-03-14
相关资源
最近更新 更多