时间与空间的权衡,为了让程序更快运行可能需要更多的内存空间,另一方面为节省内存或许需编写运行速度稍慢的代码。一个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))