R語言中的代碼運算性能提升

時間與空間的權衡,爲了讓程序更快運行可能需要更多的內存空間,另一方面爲節省內存或許需編寫運行速度稍慢的代碼。一個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))

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章