時間與空間的權衡,爲了讓程序更快運行可能需要更多的內存空間,另一方面爲節省內存或許需編寫運行速度稍慢的代碼。一個R會話中的所有對象都保存在內存中,即R的內存地址空間中,R語言已可以支持2^31字節以上的向量
1.通過向量化的方式優化R代碼
2.使用字節碼編譯
3.將R代碼中最消耗CPU的部分用編譯型語言編碼,如C/C++
4.將R代碼用並行方式編寫
5.其他的一些方法
1.循環很慢
在有循環的代碼中,涉及到大量的函數調用。如for()是一個函數、:是一個函數(置換函數)、向量取下標的操作是一個函數調用等等
調用函數是非常耗時的,因爲其中牽扯到創建堆棧幀等過程,如果在循環的每次迭代中都有這些時間損耗,那麼加起來會很耗時
2.向量化運算
有可能加速代碼運算的向量化函數:ifelse\which\where\any\all\cumsum\
cumprod,對矩陣而言有rowSums\colSums
#向量化運算有時雖然獲得了速度上的提升,但使用了更多內存
nreps=100000
xymat=matrix(rnorm(2*nreps),ncol=2)
maxs=pmax(xymat[,1],xymat[,2]) #向量化計算
print(mean(maxs))
生成冪次矩陣中的向量化思考
power1=function(x,dg){
pw=matrix(x,nrow=length(x))
prod=x
for (i in 1:dg){
prod=prod*i
pw=cbind(pw,prod) #逐列生成最終矩陣,但在內存分配上需要消耗更多時間
}
return(pw)
}
#在最開始即定義完整矩陣,分配內存
power2=function(x,dg){
pw=matrix(nrow=length(x),ncol=dg)
prod=x
for (i in 1:dg){
prod=prod*i
pw[,i]=prod
}
return(pw)
}
#使用outer函數,運算時間反而更長
power3=function(x,dg){
return(outer(x,1:dg,"^")) #FUN="^"並沒有進行向量化
}
與運算性能有關的問題很多時候是不可預測的,要儘量嘗試各種不同的方法
3.函數式編程與內存
1.向量賦值
z=c(1,2,3)
z[3]=4 #等價於z="[<-"(z,3,value=4)
上面代碼中表面上只改變向量中的一個元素,但整個語句的含義是向量被重新計算了,這對於很長的向量會極大降低程序運行速度,而較短向量在循環中被反覆賦值也會造成整體運行速度降低
2.改變時拷貝
x=c(1,2,3)
y=x
tracemem(x) #報告變量的內存再分配情況<0000000005E6BF60>
tracemem(y) #<0000000005E6BF60>
此時x與y共享相同的內存區域,當y發生改變時(x不變化),y對應的內存地址也會變化
y=2
tracemem(y) #<0000000004CC9928>
x[2]=3
tracemem(x) #<0000000004279FA0>,需要注意內存地址的變化
4.利用Rprof()尋找代碼瓶頸
Rprof(filename = "Rprof.out", append = FALSE, interval = 0.02,
memory.profiling = FALSE, gc.profiling = FALSE,
line.profiling = FALSE, numfiles = 100L, bufsize = 10000L)
Rprof()可提供一份報告,報告代碼中調用的每個函數所消耗的時間(僅作爲調優的參考使用)。Rprof每隔0.02秒(默認)R會檢查一次函數調用棧並以此決定當前有用的函數,並將結果寫入文件Rprof.out
sample.interval=20000
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"*" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
"cbind" "power1"
#power1調用cbind,第一列是當前正在執行的函數,summaryRprof()可彙總這個文件的所有行
Rprof()函數用法
power1=function(x,dg){
pw=matrix(x,nrow=length(x))
prod=x
for (i in 1:dg){
prod=prod*i
pw=cbind(pw,prod) #逐列生成最終矩陣,但在內存分配上需要消耗更多時間
}
return(pw)
}
Rprof() #開啓監視器
x=rnorm(100000)
power1(x,40) #invisible(power1(x,40))不顯示運行過程
Rprof(NULL) #NULL參數表示結束監視
summaryRprof() #查看結果,從中可看到代碼主要的運行時間花在哪個函數上
5.字節碼編譯
可利用字節碼編譯器嘗試加速代碼,但一般沒有向量化的代碼快,但字節碼編譯具有一定的提速潛力
library(compiler)
power1_com=cmpfun(power1) #創建新函數-編譯版本
system.time(power1_com(x,40))