理解代码性能问题

在诞生之初,R 就是为统计计算和数据可视化而设计的,并且被学界和业界广泛运用。
就大多数数据分析的目的而言,正确性比性能更重要。换句话说,在 1 分钟内得到正确的
结果比在 20 秒内获得错误的结果要好。速度快 3 倍的结果并不比速度慢但正确的结果好 3
倍。因此,在确认自己的代码正确之前先不必考虑性能的问题。
若百分之百地确认代码正确无误,只是运行速度比较慢时,就有必要进行代码优化从
而提升运行速度了。当然,具体情况需要具体分析。在制定决策之前,最好将解决问题的
时间划分为 3 部分:开发时间、运行时间和未来的维护时间。
假设我们处理一个问题花了 1 个小时,由于在开始时没有考虑性能问题,代码运行
的速度并不快。我们需要花 50 分钟来思考并解决问题。然后,再花 1 分钟的时间运行代
码并得到答案。由于代码和问题高度一致并且很直观,之后的改进就很容易,因此可以
节省维护时间。
假设另一个开发人员要处理相同的问题,他一开始就想写一个性能极好的代码。本身
求解问题就需要花费一定的时间,优化代码结构提升运行速度则需要花费更多的时间。这
样,想出并实现一个高性能的解法可能需要 2 个小时。然后,再用 0.1 秒的时间运行并得
到答案。代码经过了特别优化以尽可能高效率地使用硬件,但是对于未来的改进就不够灵
活,特别是当问题更新后,可能需要花费更多的时间来维护。
第二个开发人员可以很高兴地说,他的代码性能要比我们的好 600 倍,但也许是不值
得的。因为他花费了更多自己的时间。在很多情况下,我们自己的时间要比计算机的时间
宝贵的多。
但是,如果代码经常使用,例如需要迭代数十亿次,那么每次迭代的性能提升一点点,
就可以节省大量的时间。在这种情况下,代码的性能就很重要了。
举一个简单的例子:对数值向量进行累加的算法,即输出向量的每个元素是输入向量
对应元素之前的所有元素的和。接下来的讨论中,我们将在不同的语义下检验代码。
尽管 R 提供了一个内置函数 cumsum( ),为了帮助大家理解性能的问题,我们还是
自己写一个程序。上述算法很容易实现:
x <- c(1, 2, 3, 4, 5)
y <- numeric( )
sum_x <- 0
for (xi in x) {
sum_x <- sum_x + xi
y <- c(y, sum_x)
}
y
## [1] 1 3 6 10 15
这个算法只是用了 for 循环,将输入向量 x 的每个元素加到 sum_x 里。每次迭代中,
sum_x 都追加在输出向量 y 的后面。我们可以用下面这个函数的形式重写算法:
my_cumsum1 <- function(x) {
y <- numeric( )
sum_x <- 0
for (xi in x) {
sum_x <- sum_x + xi
y <- c(y, sum_x)
}
y
}
另一种实现方法是使用索引访问输入向量 x,访问或修改输出向量 y:
my_cumsum2 <- function(x) {
y <- numeric(length(x))
if (length(y)) {
y[[1]] <- x[[1]]
for (i in 2:length(x)) {
y[[i]] <- y[[i - 1]] + x[[i]]
}
}
y
}
R 提供了内置函数 cumsum( ) 做完全一样的工作。前面两个算法应该会得到与
cumsum( ) 完全相同的结果。下面我们生成一些随机数看看结果是否一致:
x <- rnorm(100)
all.equal(cumsum(x), my_ _cumsum1(x))
## [1] TRUE
all.equal(cumsum(x), my_ _cumsum2(x))
## [1] TRUE
上述代码中,all.equal( ) 检查两个向量中的对应元素是否都相等。从返回结果看,
可以确定 my_cumsum1( )、my_cumsum2( ) 和 cumsum( ) 是一致的。下一节,我们
将测试每个版本的累加算法所需要的运行时间。

posted @ 2019-02-11 14:20  NAVYSUMMER  阅读(185)  评论(0编辑  收藏  举报
交流群 编程书籍