使用内置函数
前面,我们演示了 my_cumsum1( )、my_cumsum2( ) 和内置函数 cumsum( ) 之
间的性能差异。尽管 my_cumsum2( ) 比 my_cumsum1( ) 快,但是当输入向量的数据量
很大时,cumsum( ) 要比它们都快很多。而且,随着输入向量数据量的增加,cumsum( )
的性能也不会显著降低。如果我们输入 cumsum,可以看到,它是一个原函数:
cumsum
## function (x) .Primitive("cumsum")
R 中的原函数是在 C/C++/Fortran 的环境中实现的,然后编译成本机指令,因此,效率非常
高。另一个要介绍的例子是 diff( )。现在,我们在 R 中定义一个函数,计算向量的差分序列:
diff_for <- function(x) {
n <- length(x) - 1
res <- numeric(n)
for (i in seq_ _len(n)) {
res[[i]] <- x[[i + 1]] - x[[i]]
}
res
}
可以证实这个函数正确地执行了差分运算:
diff_ _for(c(2, 3, 1, 5))
## [1] 1 -2 4
因此,对于同样的输入,diff_for( ) 和 内置函数 diff( ) 一定会返回相同的结果:
x <- rnorm(1000)
all.equal(diff_ _for(x), diff(x))
## [1] TRUE
然而,这两个函数的性能差异却非常大。
microbenchmark(diff_ _for(x), diff(x))
## Unit: microseconds
## expr min lq mean median
## diff_for(x) 1034.028 1078.9075 1256.01075 1139.1270
## diff(x) 12.057 14.2535 21.72772 17.5705
## uq max neval cld
## 1372.1145 2634.128 100 b
## 25.4525 75.850 100 a
在大多数情况下,R 的内置函数要比其他功能相同的函数快很多。对向量如此,对矩
阵也是如此。例如,下面是一个简单的 3×4 的整数矩阵:
mat <- matrix(1:12, nrow = 3)
mat
## [,1] [,2] [,3] [,4]
## [1,] 1 4 7 10
## [2,] 2 5 8 11
## [3,] 3 6 9 12
我们写一个函数来求矩阵的转置:
my_transpose <- function(x) {
stopifnot(is.matrix(x))
res <- matrix(vector(mode(x), length(x)),
nrow = ncol(x), ncol = nrow(x),
dimnames = dimnames(x)[c(2, 1)])
for (i in seq_ _len(ncol(x))) {
for (j in seq_ _len(nrow(x))) {
res[i, j] <- x[j, i]
}
}
res
}
在这个函数中,首先创建一个和输入同类型的矩阵,分别交换行数和列数、行名和列
名。然后,再通过迭代列和行,替换对应元素来转置矩阵:
my_ _transpose(mat)
## [,1] [,2] [,3]
## [1,] 1 2 3
## [2,] 4 5 6
## [3,] 7 8 9
## [4,] 10 11 12
矩阵转置的内置函数是 t( )。很容易验证这两个函数返回相同的结果:
all.equal(my_ _transpose(mat), t(mat))
## [1] TRUE
然而,它们在性能上可能会有很大的差异:
microbenchmark(my_ _transpose(mat), t(mat))
## Unit: microseconds
## expr min lq mean median uq
## my_transpose(mat) 22.795 24.633 29.47941 26.0865 35.5055
## t(mat) 1.576 1.978 2.87349 2.3375 2.7695
## max neval cld
## 71.509 100 b
## 16.171 100 a
当输入矩阵更大时,这种差异就更显著了。下面我们创建一个含有 1000 行和 25 列的
新矩阵。尽管结果相同,性能差距却非常大:
mat <- matrix(rnorm(25000), nrow = 1000)
all.equal(my_ _transpose(mat), t(mat))
## [1] TRUE
microbenchmark(my_ _transpose(mat), t(mat))
## Unit: microseconds
## expr min lq mean
## my_transpose(mat) 21786.241 22456.3990 24466.055
## t(mat) 36.611 46.2045 61.047
## median uq max neval cld
## 23821.5905 24225.142 113395.811 100 b
## 57.7505 68.694 142.126 100 a
这里需要注意的是,t( ) 是一个泛型函数,它对矩阵和数据框都适用。S3 方法分派
会为输入寻找正确的方法,也会有一些耗时开销。因此,对于矩阵输入,直接调
用 t.default( ) 会稍快一点:
microbenchmark(my_ _transpose(mat), t(mat), t.default(mat))
## Unit: microseconds
## expr min lq mean
## my_transpose(mat) 21773.751 22498.6420 23673.26089
## t(mat) 37.853 48.8475 63.57713
## t.default(mat) 35.518 41.0305 52.97680
## median uq max neval cld
## 23848.6625 24139.7675 29034.267 100 b
## 61.3565 69.6655 140.061 100 a
## 46.3095 54.0655 146.755 100 a
前面所有的例子都说明,在 R 中,大多数情况下,最好使用内置函数(如果 R 提供了
的话)而不是“重造轮子”。因为,内置函数直接调用 C 代码,从而节省了 R 代码所花费
的时间,即使在输入数据量巨大的时候,还是极其高效的。