用向量化提升速度
用向量化操作替代显式循环,大幅提升运行速度
用向量化提升速度 是 CoddyKit 上的免费 R Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。
为什么向量化很重要
R 是一种解释型语言,因此 for 循环的每次迭代都会产生额外开销,包括函数调用分派、边界检查和类型强制转换。向量化操作会将这些工作交给编译后的 C 代码执行,其速度通常会快几个数量级。
向量化是 R 基础包中影响最大的一种优化方式。
循环与 cumsum() 示例
分别使用 for 循环和内置的 cumsum() 计算累计总和,可以清楚地看到两者的差距。cumsum() 会调用 C 层的编译代码,一次遍历即可处理整个向量。
n <- 500000
x <- rnorm(n)
t_loop <- system.time({
result <- numeric(n)
result[1] <- x[1]
for (i in 2:n) result[i] <- result[i-1] + x[i]
})['elapsed']
t_vec <- system.time({
result2 <- cumsum(x)
})['elapsed']
cat('Loop :', t_loop, 's
')
cat('cumsum:', t_vec, 's
')ifelse() 与 for + if
ifelse(condition, yes, no) 是一种向量化条件函数,可以一次性在整个向量上计算条件。它用一次 C 层遍历替代了逐元素执行的 for + if 循环。
n <- 300000
x <- rnorm(n)
t_loop <- system.time({
result <- numeric(n)
for (i in seq_len(n)) result[i] <- if (x[i] > 0) x[i] else -x[i]
})['elapsed']
t_vec <- system.time({
result2 <- ifelse(x > 0, x, -x)
})['elapsed']
cat('for+if :', t_loop, 's
')
cat('ifelse :', t_vec, 's
')预分配结果向量
当循环无法避免时,请在循环开始前预分配结果向量。在循环中使用 c(result, new_val) 会在每次迭代时复制整个向量,导致总内存操作数达到 O(n^2)。
n <- 20000
t_grow <- system.time({
result <- c()
for (i in seq_len(n)) result <- c(result, i^2)
})['elapsed']
t_prealloc <- system.time({
result2 <- numeric(n)
for (i in seq_len(n)) result2[i] <- i^2
})['elapsed']
cat('Growing vector:', t_grow, 's
')
cat('Pre-allocated :', t_prealloc, 's
')选择正确的预分配类型
使用与数据匹配的类型构造函数,避免预分配过程中发生隐式强制转换:
numeric(n)— 双精度浮点数integer(n)— 整数character(n)— 空字符串logical(n)— FALSE 值vector('list', n)— 由 NULL 组成的列表
n <- 5
cat('numeric :', numeric(n), '
')
cat('integer :', integer(n), '
')
cat('logical :', logical(n), '
')
cat('character:', character(n), '
')
cat('list len :', length(vector('list', n)), '
')colSums() 和 rowSums() 与 apply() 的比较
对于矩阵操作,colSums(m)、rowSums(m)、colMeans(m) 和 rowMeans(m) 都是经过高度优化的 C 例程。它们始终比 apply(m, 1, sum) 更快,因为后者会针对每一行分派一次 R 函数 sum。
m <- matrix(rnorm(1000 * 2000), nrow = 1000)
t_apply <- system.time(apply(m, 2, sum))['elapsed']
t_colsums <- system.time(colSums(m))['elapsed']
cat('apply(m,2,sum):', t_apply, 's
')
cat('colSums(m) :', t_colsums, 's
')向量化算术始终很快
向量上的基本算术运算——+、-、*、/、^、sqrt()、log() 和 exp()——全部支持向量化。它们会在一次 C 调用中,对整个向量逐元素执行运算。请始终优先使用它们,而不是循环。
x <- 1:1000000
t1 <- system.time(y <- x^2 + 2*x + 1)['elapsed']
t2 <- system.time({
y2 <- numeric(length(x))
for (i in seq_along(x)) y2[i] <- x[i]^2 + 2*x[i] + 1
})['elapsed']
cat('Vectorized:', t1, 's
')
cat('Loop :', t2, 's
')使用逻辑子集而不是循环
使用逻辑条件筛选向量是向量化操作。与其循环并根据条件追加元素,不如创建逻辑索引后一次性取子集——底层 C 代码只需遍历一次。
x <- rnorm(500000)
t_loop <- system.time({
pos <- c()
for (v in x) if (v > 0) pos <- c(pos, v)
})['elapsed']
t_vec <- system.time({
pos2 <- x[x > 0]
})['elapsed']
cat('Loop filter:', t_loop, 's
')
cat('Logical idx:', t_vec, 's
')使用 which() 和 tabulate() 处理索引
当您需要获取 TRUE 值的位置时,which(condition) 是向量化且快速的。对于稠密的整数范围,tabulate(bin_vector) 统计整数出现次数的速度比 table() 更快。
x <- sample(1:10, 100000, replace = TRUE)
t_table <- system.time(table(x))['elapsed']
t_tabulate <- system.time(tabulate(x, nbins = 10))['elapsed']
cat('table() :', t_table, 's
')
cat('tabulate():', t_tabulate, 's
')
idx <- which(x == 5)
cat('Positions of 5: first 5 =', head(idx, 5), '
')循环仍然适用的情况
并非所有循环都不好。在以下情况下,使用循环是可以接受的:
- 每次迭代都依赖上一次的结果(顺序依赖)
- 迭代次数较少(< 1000)
- 循环体调用了没有向量化等价实现的复杂函数
在这些情况下,应重点进行预分配,并避免在循环内部不断扩展数据结构。
# Sequential dependency -- loop is correct here
fib <- function(n) {
result <- integer(n)
result[1] <- 1L
if (n >= 2) result[2] <- 1L
for (i in seq_len(n)[-c(1,2)]) result[i] <- result[i-1] + result[i-2]
result
}
cat('Fibonacci:', fib(10), '
')向量化总结
编写快速 R 代码时的向量化关键规则:
- 使用
cumsum/cumprod/diff进行顺序累积 - 使用
ifelse()处理逐元素条件 - 使用
numeric(n)/vector('list',n)进行预分配 - 对于
apply(),优先使用colSums/rowSums/colMeans/rowMeans - 逻辑子集优于筛选循环
快速检查:预分配
为什么在循环中使用 result <- c(result, new_val) 扩展向量时,对于较大的 n 会非常慢?
向量化回顾
向量化是 R 提升性能的主要手段:
- 向量化函数(
cumsum、ifelse、算术运算符)会调用已编译的 C 代码,速度比功能等价的 R 循环快 10 到 100 倍 - 在任何不可避免的循环开始前预分配结果容器,以避免 O(n^2) 的复制
- 对于矩阵聚合,
colSums/rowSums的速度优于apply() - 逻辑子集能够简洁快速地替代筛选循环
常见问题解答
「用向量化提升速度」课时是免费的吗?
是的 — 「用向量化提升速度」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。
「用向量化提升速度」这节课中我会学到什么?
用向量化操作替代显式循环,大幅提升运行速度 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 R Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「用向量化提升速度」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 R Academy 课中编写并运行代码吗?
能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。