R 语言的一大优点就是其编写效率高,追求极致的运行效率并不是 R 的关注点。使用一些基础的计算库,例如 OpenBLAS 和 Intel MKL,可以一定程度提升 R 的运行速度,主要是矩阵运算方面。前几年微软发布过自带 MKL 的 Microsoft R Open(MRO),但并没有持续更新,最终版本停留在了3.5.3,而目前最新的 R-3 版本为3.6.3,R-4.0.2也已经可以下载了。感谢 MKL 的免费,现在可以手动将其与最新版本的R进行链结了。

下列操作方法来自 stack overflow,我不过是做了一些搬运和翻译的工作。另外,我使用的是 Intel CPU 和 Win 系统,在 github 上有用户提供了 Ubuntu 安装 MKL 以及使得 R 调用 MKL 的方法,不过在 AMD CPU,或是 Mac OS和 Linux 的其他发行版上是否有类似的方法或是起到类似的功效,我并不了解。【毕竟有 MATLAB 调用 MKL 结果对 AMD CPU 进行负优化这一情况的存在,不过听说 MALTAB2020 解决了这一问题?】

1. 具体操作方法

1.1 下载 MKL

首先在 MKL 的官网下载对应的 Windows 版本。下载前可能需要填写并提交一些信息,接着在下拉菜单中选择Intel® Math Kernel Library for Windows,版本可以选择最新的,最终选择 Full Package 开始下载。我也把安装包分享到了百度云,提取码 5g4j 。

1.2 安装 MKL 并与 R 链结

初步下载下来的是一个包装成 .exe 文件的压缩包,先双击执行文件然后选择一个合适的解压目录,下方的勾选框可以勾选上,安装完成后会自动删除解压出的临时文件。解压完成之后就会自动启动安装程序,随意选择合适的安装目录即可。安装完成后,将下列两个目录内部的所有文件

D:\Software\ToolsADV\MKL\compilers_and_libraries_2020.2.254\windows\redist\intel64_win\compiler\ D:\Software\ToolsADV\MKL\compilers_and_libraries_2020.2.254\windows\redist\intel64_win\mkl\

拷贝至下列 R 安装目录内的一个文件夹中

D:\Software\ToolsADV\R\R4\bin\x64

其中蓝色部分因人而异,请根据个人的 MKL 和 R 的安装路径以及 MKL 版本自行调整。接着在目前这个 x64 文件夹中,将文件 mkl_rt.dll 复制两份于当前文件夹,其中一个重命名为 Rblas.dll,另一个重命名为 Rlapack.dll,覆盖当前文件夹中原本存在的两个同名文件。到此就算是完成啦。

Remark. 在从 mkl 和 compiler 文件夹拷贝文件时,是把这两个文件夹内部的文件拷贝进 x64 文件夹,而不是把这两个文件夹拷贝进 x64 。

2. 一些小的示例

有兴趣的同学可以专门另外安装一个不带 MKL 版本的 R,来比较使用 MKL 前后的速度差异。

my.time <- matrix(0, nr = 4, nc = 5)
rownames(my.time) <- paste0('Example ', 1:4)
colnames(my.time) <- names(system.time(NULL))

set.seed(1)
m <- 10000
n <- 5000
A <- matrix(runif(m*n), m, n)

# Matrix multiplication
my.time[1,] <- system.time(B <- crossprod(A))

# Cholesky Factorization
my.time[2,] <- system.time(C <- chol(B))


n <- 2000
A <- matrix(runif(m*n), m, n)

# Singular Value Decomposition
my.time[3,] <- system.time(S <- svd(A))

# Principal Components Analysis
my.time[4,] <- system.time(P <- prcomp(A))


my.time[,1:3] %>% 
  kable(format = 'html', escape = F) %>%
  kable_styling('striped', full_width = F)
user.self sys.self elapsed
Example 1 6.72 1.21 1.35
Example 2 1.37 0.03 0.23
Example 3 18.92 0.44 3.38
Example 4 22.00 0.50 4.03

这里的用户时间大于流逝时间的原因应该是用户时间是多个 CPU 核心的时间累加得出的结果,MKL 库会利用并行计算实现加速。在安装 MKL 之前,流逝时间一般与用户时间和系统时间之和比较接近,因为都是用单线程在跑。

3. 后记

上文主要介绍了如何将 MKL 与 R 链结起来,以提升 R 的运行速度。不过说个题外话,个人认为 MKL 对于 R 起的是锦上添花的作用,对于初学者,把更多的精力放在优化代码上可能更加重要。在进行统计计算时,R代码运行速度如果非常缓慢,一方面可能是算法本身还需进一步优化,另一方面可能是代码不够高效。比如简单粗暴的 for 循环套 for 循环看似非常直观,但实际运算效率非常低下。优化 R 代码的方式有许多,首先,有些看似繁杂的操作可能使用 R 自带的内建函数就能完成,或是使用 apply 函数家族代替 for 循环就能更快捷地满足要求。在网上也能找到一些优化代码的tips。另外,在提出新方法时,初步跑通的代码不一定是最优化的,运行时的 CPU 占用可能跑不满,甚至只有20-30%,但又想先使用模拟的方式初步验证新方法的统计性能。这时使用并行计算的方式,同时跑多个模拟,能够节省大量的时间。最后,更为高阶的提速方式,是将算法的核心部分写成内嵌的 C 或者是 Fortran 语言。

总的来讲,写出好看又高效的 R 代码不是一件容易的事情,需要一定的学习思考以及阅读积累,有兴趣的同学可以多关注统计之都论坛谢益辉师兄的博客,以及其它一些书籍等等的资料,比如Efficient R programming.