Using R: 谨慎在读写性能糟糕的 WSL2 中使用 R!速度只有1/10

最近跑的一段程序,其中需要对栅格文件进行批量读写,然而使用多 session 的并行无法批量处理 raster 文件,而基于 fork 策略的并行方法又无法在 Windows 中调用。于是只能考虑在 Unix 或者 Linux 中完成。然而,手头 MacBook 性能不足,PC 上面虽然装了 Ryzentosh 但又不想重启,自然而然想到了 WSL2,虽然对于它低下的 IO 性能早有耳闻,然而本着又不是不能用的心态,就这么用了起来。

完成当前任务之后,顺带手打算再跑其它业务,然而就在最开始读取 csv 文件时,事情开始不对劲了,原来只要几秒的读取,怎么这边搞了半天还不停?

之后查阅资料翻看 WSL2 的官方文档,都有提到 WSl2 各种好,然而磁盘 IO 似乎有一丢丢问题。这可能就是影响我们程序运行的表现。

实验与材料

计算机

Hardware

AMD 3600 + B450M Mortar + 16*2GB DDR4 2999 (Overclocked from 2666) + SanDisk Ultra NVME 1T

OS + WSL2

Ubuntu 20.04 over Windows 11, 所有系统和组建均已升级至截止 2021 年 9 月 1 日的最新版本(抱歉,我忘记记录版本号了)

实验方法

WSL2 官方文档中的介绍,WSL2 低 IO 性能表现,主要是在 WSL2 和宿主机之间交换数据过程中表现明显,即使用 WSL2 中的 OS 读取宿主机分区中的文件(或反向),因此我们的实验目录就是用 Ubuntu 中的 R 读取 WIndows 11 C 盘根目录下 Data 文件中的 tb_gridded.csv 文件。该文件具有 75 列 3567666 行,大小 2.65GB,连续读取 10 次,观察读取时间。代码如下:

library("data.table")
library("microbenchmark")

data.table::setDTthreads(threads = 0)

result_benchmark <-
    microbenchmark(NULL, fread("Data/tb_gridded.csv"), times = 10)

as.data.table(print(result_benchmark,
    unit = "s",
    signif = 3
))

为了提高读取速度,我们使用 data.table::fread 来读入文件,并通过 data.table::setDTthreads(threads = 0) 设定为调用所有 12 个逻辑线程。

实验结果

由于读取过程比较久,这里我们直接放结果,并非本 RMarkdown 文件实时编译结果:

## Unit: seconds
##                          expr      min       lq     mean  median       uq
##                          NULL 1.00e-09 1.10e-08 9.10e-08 1.1e-08 2.10e-08
##  fread("Data/tb_gridded.csv") 2.13e+01 2.15e+01 2.31e+01 2.2e+01 2.28e+01
##       max neval cld
##  7.91e-07    10  a 
##  3.17e+01    10   b

从上面的表格中我们可以看出,10 次读取中,速度最快的一次耗时 21 秒,速度最慢的一次耗时 31 秒,平均耗时 23 秒。

对于同样的文件,在宿主机中读取:

## Unit: seconds
##                          expr  min   lq     mean median   uq      max neval cld
##                          NULL 0.00 0.00 5.00e-08   0.00 0.00 5.00e-07    10  a 
##  fread("Data/tb_gridded.csv") 2.43 2.46 2.79e+00   2.58 2.67 4.48e+00    10   b

此时最快 2.46 秒,最慢 4,48 秒,平均 2.79 秒.

在同一台电脑的 Ryzentosh 环境中重复了测试:

## Unit: seconds
##                          expr      min       lq     mean   median       uq
##                          NULL 9.00e-09 2.90e-08 9.40e-08 5.40e-08 1.69e-07
##  fread("Data/tb_gridded.csv") 2.46e+00 2.49e+00 2.91e+00 2.52e+00 2.67e+00
##       max neval cld
##  2.29e-07    10  a 
##  6.19e+00    10   b

在相同配置的 Ryzentosh 中,读取上述文件最快 2.46 秒,最慢 6.19 秒,平均耗时 2.91 秒。由于同样是支持 POSIX 的操作系统,因此可以一定程度上消除系统层面差异带来的影响。

通过比较同配置下 WSL2 与宿主机不同系统通过 data.table 读取文件可以发现,两种环境下速度差距在 10 倍左右。

但如果非要使用 WSL2 进行文件读写密集型的操作该怎么办呢?将文件复制到 WSL2 中的目录中去。例如笔者将上述文件复制到 WSL2 的 Ubuntu 用户根目录下再次运行:

## Unit: seconds
##                          expr      min       lq     mean  median       uq
##                          NULL 1.00e-09 1.00e-09 3.90e-08 1.1e-08 1.10e-08
##  fread("Data/tb_gridded.csv") 2.21e+00 2.45e+00 2.63e+00 2.6e+00 2.68e+00
##       max neval cld
##  2.71e-07    10  a 
##  3.43e+00    10   b

此时最快 2.21 秒,最慢 2.43 秒,平均 2.63 秒,这样甚至是最快的!

综上,是不是使用 WSL2,该如何使用 WSL2,希望能给大家更多启发。


欢迎通过邮箱微博, Twitter以及知乎与我联系。也欢迎关注我的博客。如果能对我的 Github 感兴趣,就再欢迎不过啦!