高級編程--編寫有效的代碼
在程序員中間流傳着一句話:“優秀的程序員是花一個小時來調試代碼而使得它的運算速度提高一秒的人。”R是一種鮮活的語言,大多數用戶不用擔心寫不出高效的代碼。作為一般規則,讓代碼易於理解、易於維護比優化它的速度更重要。但是當你使用大型數據集或處理高度重復的任務時,速度就成為一個問題了。
幾種編碼技術可以使你的程序更高效:
q 程序只讀取需要的數據。
q 盡可能使用矢量化替代循環。
q 創建大小正確的對象,而不是反復調整。
q 使用並行來處理重復、獨立的任務。
1. 有效的數據輸入
使用read.table()函數從含有分隔符的文本文件中讀取數據的時候,你可以通過指定所需的變量和它們的類型實現顯著的速度提升。這可以通過包含colClasses參數的函數來實現。例如,假設你想在用逗號分隔的、每行10個變量的文件中獲得3個數值變量和2個字符變量。數值變量的位置是1、2和5,字符變量的位置是3和7。在這種情況下,代碼:
my.data.frame <- read.table(mytextfile, header=TRUE, sep=',',
colClasses=c("numeric", "numeric", "character",
NULL, "numeric", NULL, "character", NULL,
NULL, NULL))
將比下面的代碼運行得更快:
my.data.frame <- read.table(mytextfile, header=TRUE, sep=',')
2. 矢量化
在有可能的情況下盡量使用矢量化,而不是循環。這里的矢量化意味着使用R中的函數,這
些函數旨在以高度優化的方法處理向量。初始安裝時自帶的函數包括 ifelse()、colsums()、
rowSums()和rowMeans()。matrixStats包提供了很多進行其他計算的優化函數,包括計數、
求和、乘積、集中趨勢和分散性、分位數、等級和分級的措施。plyr、dplyr、reshape2和
data.table等包也提供了高度優化的函數。
考慮一個1 000 000行10列的矩陣。讓我們使用循環並且再次使用colSums()函數來計算列的和。首先,創建矩陣:
set.seed(1234)
mymatrix <- matrix(rnorm(10000000), ncol=10)
然后,創建一個accum()函數來使用for循環獲得列的和:
accum <- function(x){
sums <- numeric(ncol(x))
for (i in 1:ncol(x)){
for(j in 1:nrow(x)){
sums[i] <- sums[i] + x[j,i]
}
}
}
system.time()函數可以用於確定CPU的數量和運行該函數所需的真實時間:
system.time(accum(mymatrix))

使用colSums()函數計算和的時間:
system.time(colSums(mymatrix))

結果分析:很明顯,矢量化的運行速度更快
3. 並行化
並行化包括分配一個任務,在兩個或多個核同時運行組塊,並把結果合在一起。這些內核可能是在同一台計算機上,也可能是在一個集群中不同的機器上。需要重復獨立執行數字密集型函數的任務很可能從並行化中受益。這包括許多蒙特卡羅方法(Monte Carlo method),如自助法(bootstrapping)。
R中的許多包支持並行化,參見Dirk Eddelbuettel的“CRAN Task View: High-Performance and Parallel Computing with R”(http://mng.bz/65sT)。在本節中,你可以使用foreach和doParallel
包在單機上並行化運行。foreach包支持 foreach循環構建(遍歷集合中的元素)同時便於並行執行循環。doParallel包為foreach包提供了一個平行的后端。
(1)foreach和doParallel包的並行化
#(1)加載包並登記內核數量
install.packages("foreach")
library(foreach)
install.packages("doParallel")
library(doParallel)
library(iterators)
library(parallel)
registerDoParallel(cores=4)
#(2)定義函數,在這里分析100 000×100的隨機數據矩陣。使用foreach和%do%執行eig()函數500次
eig <- function(n, p){
x <- matrix(rnorm(100000), ncol=100)
r <- cor(x)
eigen(r)$values
}
n <- 1000000
p <- 100
k <- 500
#(3)正常執行
system.time(
x <- foreach(i=1:k, .combine=rbind) %do% eig(n, p)
)

#(4)並行執行,system.time( %do%操作符按順序運行函數,.combine=rbind操作符追加對象x作為行。最后,函數使用%dopar%操作符進行並行運算
x <- foreach(i=1:k, .combine=rbind) %dopar% eig(n, p)
)

