R語言編程的高效方法
學習資料來源:
- datacamp : writing-efficient-r-code
- 網絡資源:
https://cosx.org/2016/09/r-and-parallel-computing
http://gforge.se/2015/02/how-to-go-parallel-in-r-basics-tips/
高效編程之細節知識點
- 1.使用最新的R版本 通過version指令查看
- 2.r中讀取rds形式的文件速度快於csv文件. readRDS()
- 3.通過system.time查看函數的一次運行時間
- 4.通過microbenchmark包中的microbenchmark()查看函數的一次運行時間
library(microbenchmark)
compare <- microbenchmark(read.csv('x.csv'),
readRDS('x.rds'),times=10)
times=10表示過程重複10遍,一次運行函數需要的時間是隨機的
- 5.電腦硬件越好當然越快
通過benchmarkme包可以查看電腦硬件性能
library(benchmarkme)
ram <- get_ram()
cpu <- get_cpu()
R的存儲方式
從內存角度來看,R 採用的是內存計算模式(In-Memory),被處理的數據需要預取到主存(RAM)中。其優點是計算效率高、速度快,但缺點是這樣一來能處理的問題規模就非常有限(小於 RAM 的大小)。另一方面,R 的核心(R core)是一個單線程的程序。因此,在現代的多核處理器上,R 無法有效地利用所有的計算內核。
- 6.向量化處理 (善用for循環)
常聽聞的r中使用for loop的速度是緩慢的,這樣的論斷是錯誤的,往往是因爲在for循環中,我們通過循環堆疊增長向量
錯誤示範:
x <- NULL
n <- 30000
for(i in 1:n) {
x <- c(x, rnorm(1))
}
- 7.data.frame&matrice
數據結構
dataframe行數據類型不同,列數據類型同
matrice行數據類型同,列數據類型同
可以理解:
讀取matrice的速度快於data.frame
讀取matrice列的速度快於讀取dataframe的列
讀取matrice行的速度遠快於讀取dataframe的行
- 8.用profvis查看每行函數的運行速度
# Load the data set
data(movies, package = "ggplot2movies")
# Load the profvis package
library(profvis)
# Profile the following code with the profvis function
profvis({
comedies <- movies[movies$Comedy == 1, ]
plot(comedies$year, comedies$rating)
model <- loess(rating ~ year, data = comedies)
j <- order(comedies$year)
lines(comedies$year[j], model$fitted[j], col = "red")
} )
並行計算
並行計算技術是爲了在實際應用中解決單機內存容量和單核計算能力無法滿足計算需求的問題而提出的。因此,並行計算技術將非常有力地擴充 R 的使用範圍和場景。
1.查看核數
library(parallel)
no_of_cores <- detectCores()
2.可以使用並行計算的場景
The ith value does not depend on the previous value.
3.Parellel 包
用一元二次方程求解問題來介紹如何利用 * apply 和 foreach 做並行化計算
# Not vectorized function
solve.quad.eq <- function(a, b, c) {
# Not validate eqution: a and b are almost ZERO
if(abs(a) < 1e-8 && abs(b) < 1e-8) return(c(NA, NA) )
# Not quad equation
if(abs(a) < 1e-8 && abs(b) > 1e-8) return(c(-c/b, NA))
# No Solution
if(b*b - 4*a*c < 0) return(c(NA,NA))
# Return solutions
x.delta <- sqrt(b*b - 4*a*c)
x1 <- (-b + x.delta)/(2*a)
x2 <- (-b - x.delta)/(2*a)
return(c(x1, x2))
}
# Generate data
len <- 1e6
a <- runif(len, -10, 10)
a[sample(len, 100,replace=TRUE)] <- 0
b <- runif(len, -10, 10)
c <- runif(len, -10, 10)
apply 實現方式:下面的代碼利用 lapply 函數將方程求解函數 solve.quad.eq 映射到每一組輸入數據上,返回值保存到列表裏。
# serial code
system.time(
res1.s <- lapply(1:len, FUN = function(x) { solve.quad.eq(a[x], b[x], c[x])})
)
接下來,我們利用 parallel 包裏的 mcLapply (multicores)來並行化 lapply 中的計算。從 API 的接口來看,除了額外指定所需計算核心之外,mcLapply 的使用方式和原有的 lapply 一致,這對用戶來說額外的開發成本很低。
對於Mac用戶,可以使用 parallel 包裏的 parLapply 函數來實現並行化。
在使用 parLapply 函數之前,我們首先需要建立一個計算組(cluster)。計算組是一個軟件層次的概念,它指我們需要創建多少個 R 工作進程(parallel 包會創建新的 R 工作進程,而非 multicores 裏 R 父進程的副本)來進行計算,理論上計算組的大小並不受硬件環境的影響。比如說我們可以創建一個大小爲 1000 的計算組,即有 1000 個 R 工作進程。 但在實際使用中,我們通常會使用和硬件計算資源相同數目的計算組,即每個 R 工作進程可以被單獨映射到一個計算內核。如果計算羣組的數目多於現有硬件資源,那麼多個 R 工作進程將會共享現有的硬件資源。
如下例我們先用 detectCores 確定當前電腦中的內核數目。值得注意的是 detectCores 的默認返回數目是超線程數目而非真正物理內核的數目。例如在我的筆記本電腦上有 2 個物理核心,而每個物理核心可以模擬兩個超線程,所以 detectCores() 的返回值是 4。對於很多計算密集型任務來說,超線程對性能沒有太大的幫助,所以使用logical=FALSE參數來獲得實際物理內核的數目並創建一個相同數目的計算組。由於計算組中的進程是全新的 R 進程,所以在父進程中的數據和函數對子進程來說並不可見。因此,我們需要利用 clusterExport 把計算所需的數據和函數廣播給計算組裏的所有進程。最後 parLapply 將計算平均分配給計算組裏的所有 R 進程,然後收集合並結果。
#Cluster on Windows
cores <- detectCores(logical = FALSE)
cl <- makeCluster(cores)
clusterExport(cl, c('solve.quad.eq', 'a', 'b', 'c'))
system.time(
res1.p <- parLapply(cl, 1:len, function(x) { solve.quad.eq(a[x], b[x], c[x]) })
)
stopCluster(cl)