我刚刚意识到使用
sapply(df, sum)
比 base::colSums(df)
更快。使用矩阵时也是如此 base::colSums(M)
。为什么?我错过了什么吗?一直都是这样吗?
10K×10K数据帧的基准测试结果:
$ Rscript --vanilla speed_test.R
Unit: milliseconds
expr min lq mean median uq max neval cld
sapply 125.76717 125.77749 126.21036 125.7878 126.4320 127.07610 3 a
colSums 288.09562 293.57566 298.28873 299.0557 303.3853 307.71486 3 b
colSums_M 137.68780 139.08794 141.25548 140.4881 143.0393 145.59055 3 c
colSums2 55.49845 55.86153 56.04262 56.2246 56.3147 56.40479 3 d
注意: AlmaLinux 9.5 上的 R 版本 4.4.2 (2024-10-31)。 NETLIB 或 OPENBLAS-OPENMP(无关紧要),AMD Ryzen 7 7700X。
代码:
set.seed(42)
m <- 1e4; n <- 1e4
M <- matrix(rnorm(m*n), m, n)
df <- data.frame(M)
options(width=200)
microbenchmark::microbenchmark(
sapply=sapply(df, sum),
colSums=colSums(df),
colSums_M=colSums(M),
colSums2=matrixStats::colSums2(M),
times=3L,
check='equivalent'
) |> print()
原因是 data.frame 被强制转换为矩阵,这是一个昂贵的操作。我们可以分析
colSums
:
Rprof(tmp <- tempfile(), interval = 0.002)
y <- colSums(df)
Rprof(NULL)
summaryRprof(tmp)
unlink(tmp)
在我的系统上,“by.total”中排名靠前的条目是:
# total.time total.pct self.time self.pct
#"colSums" 0.048 96 0.018 36
#"as.matrix.data.frame" 0.030 60 0.002 4
#"as.matrix" 0.030 60 0.000 0
#"unlist" 0.022 44 0.022 44
可以看到总时间的60%都花在了
as.matrix
。 R 循环比 as.matrix(x)
和 C 循环的组合更快。
此外,从算法的角度来看,循环列表的元素比循环向量并跟踪元素属于哪一列更快。
PS:您不应该在这里使用微基准测试,因为垃圾收集器可能会扭曲计时。最好使用 bench 包,它可以过滤掉
gc
处于活动状态的迭代。