This message is about a curious difference in timing between two ways of computing the same function. One uses expm, so is expected to be a bit slower, but "a bit" turned out to be a factor of >1000. The code is below. We would be grateful if anyone can point out any egregious bad practice in our code, or enlighten us on why one approach is so much slower than the other. The problem arose in an activity to develop guidelines for nonlinear modeling in ecology (at NCEAS, Santa Barbara, with worldwide participants), and we will be trying to include suggestions of how to prepare problems like this for efficient and effective solution. The code for nlogL was the "original" from the worker who supplied the problem. Best, John Nash -------------------------------------------------------------------------------------- cat("mineral-timing.R == benchmark MIN functions in R\n") # J C Nash July 31, 2011 require("microbenchmark") require("numDeriv") library(Matrix) library(optimx) # dat<-read.table('min.dat', skip=3, header=FALSE) # t<-dat[,1] t <- c(0.77, 1.69, 2.69, 3.67, 4.69, 5.71, 7.94, 9.67, 11.77, 17.77, 23.77, 32.77, 40.73, 47.75, 54.90, 62.81, 72.88, 98.77, 125.92, 160.19, 191.15, 223.78, 287.70, 340.01, 340.95, 342.01) # y<-dat[,2] # ?? tidy up y<- c(1.396, 3.784, 5.948, 7.717, 9.077, 10.100, 11.263, 11.856, 12.251, 12.699, 12.869, 13.048, 13.222, 13.347, 13.507, 13.628, 13.804, 14.087, 14.185, 14.351, 14.458, 14.756, 15.262, 15.703, 15.703, 15.703) ones<-rep(1,length(t)) theta<-c(-2,-2,-2,-2) nlogL<-function(theta){ k<-exp(theta[1:3]) sigma<-exp(theta[4]) A<-rbind( c(-k[1], k[2]), c( k[1], -(k[2]+k[3])) ) x0<-c(0,100) sol<-function(t)100-sum(expm(A*t)%*%x0) pred<-sapply(dat[,1],sol) -sum(dnorm(dat[,2],mean=pred,sd=sigma, log=TRUE)) } getpred<-function(theta, t){ k<-exp(theta[1:3]) sigma<-exp(theta[4]) A<-rbind( c(-k[1], k[2]), c( k[1], -(k[2]+k[3])) ) x0<-c(0,100) sol<-function(tt)100-sum(expm(A*tt)%*%x0) pred<-sapply(t,sol) } Mpred <- function(theta) { # WARNING: assumes t global kvec<-exp(theta[1:3]) k1<-kvec[1] k2<-kvec[2] k3<-kvec[3] # MIN problem terbuthylazene disappearance z<-k1+k2+k3 y<-z*z-4*k1*k3 l1<-0.5*(-z+sqrt(y)) l2<-0.5*(-z-sqrt(y)) val<-100*(1-((k1+k2+l2)*exp(l2*t)-(k1+k2+l1)*exp(l1*t))/(l2-l1)) } # val should be a vector if t is a vector negll <- function(theta){ # non expm version JN 110731 pred<-Mpred(theta) sigma<-exp(theta[4]) -sum(dnorm(dat[,2],mean=pred,sd=sigma, log=TRUE)) } theta<-rep(-2,4) fand<-nlogL(theta) fsim<-negll(theta) cat("Check fn vals: expm =",fand," simple=",fsim," diff=",fand-fsim,"\n") cat("time the function in expm form\n") tnlogL<-microbenchmark(nlogL(theta), times=100L) tnlogL cat("time the function in simpler form\n") tnegll<-microbenchmark(negll(theta), times=100L) tnegll ftimes<-data.frame(texpm=tnlogL$time, tsimp=tnegll$time) # ftimes boxplot(log(ftimes)) title("Log times in nanoseconds for matrix exponential and simple MIN fn")
I think one difference is that negll() is fully vectorized - no loops, whereas nlogL calls the function sol() inside sapply, i.e. a loop. Michael On 17 Aug 2011, at 10:27AM, John C Nash wrote:> This message is about a curious difference in timing between two ways of computing the > same function. One uses expm, so is expected to be a bit slower, but "a bit" turned out to > be a factor of >1000. The code is below. We would be grateful if anyone can point out any > egregious bad practice in our code, or enlighten us on why one approach is so much slower > than the other. The problem arose in an activity to develop guidelines for nonlinear > modeling in ecology (at NCEAS, Santa Barbara, with worldwide participants), and we will be > trying to include suggestions of how to prepare problems like this for efficient and > effective solution. The code for nlogL was the "original" from the worker who supplied the > problem. > > Best, > > John Nash > > -------------------------------------------------------------------------------------- > > cat("mineral-timing.R == benchmark MIN functions in R\n") > # J C Nash July 31, 2011 > > require("microbenchmark") > require("numDeriv") > library(Matrix) > library(optimx) > # dat<-read.table('min.dat', skip=3, header=FALSE) > # t<-dat[,1] > t <- c(0.77, 1.69, 2.69, 3.67, 4.69, 5.71, 7.94, 9.67, 11.77, 17.77, > 23.77, 32.77, 40.73, 47.75, 54.90, 62.81, 72.88, 98.77, 125.92, 160.19, > 191.15, 223.78, 287.70, 340.01, 340.95, 342.01) > > # y<-dat[,2] # ?? tidy up > y<- c(1.396, 3.784, 5.948, 7.717, 9.077, 10.100, 11.263, 11.856, 12.251, 12.699, > 12.869, 13.048, 13.222, 13.347, 13.507, 13.628, 13.804, 14.087, 14.185, 14.351, > 14.458, 14.756, 15.262, 15.703, 15.703, 15.703) > > > ones<-rep(1,length(t)) > theta<-c(-2,-2,-2,-2) > > > nlogL<-function(theta){ > k<-exp(theta[1:3]) > sigma<-exp(theta[4]) > A<-rbind( > c(-k[1], k[2]), > c( k[1], -(k[2]+k[3])) > ) > x0<-c(0,100) > sol<-function(t)100-sum(expm(A*t)%*%x0) > pred<-sapply(dat[,1],sol) > -sum(dnorm(dat[,2],mean=pred,sd=sigma, log=TRUE)) > } > > getpred<-function(theta, t){ > k<-exp(theta[1:3]) > sigma<-exp(theta[4]) > A<-rbind( > c(-k[1], k[2]), > c( k[1], -(k[2]+k[3])) > ) > x0<-c(0,100) > sol<-function(tt)100-sum(expm(A*tt)%*%x0) > pred<-sapply(t,sol) > } > > Mpred <- function(theta) { > # WARNING: assumes t global > kvec<-exp(theta[1:3]) > k1<-kvec[1] > k2<-kvec[2] > k3<-kvec[3] > # MIN problem terbuthylazene disappearance > z<-k1+k2+k3 > y<-z*z-4*k1*k3 > l1<-0.5*(-z+sqrt(y)) > l2<-0.5*(-z-sqrt(y)) > val<-100*(1-((k1+k2+l2)*exp(l2*t)-(k1+k2+l1)*exp(l1*t))/(l2-l1)) > } # val should be a vector if t is a vector > > negll <- function(theta){ > # non expm version JN 110731 > pred<-Mpred(theta) > sigma<-exp(theta[4]) > -sum(dnorm(dat[,2],mean=pred,sd=sigma, log=TRUE)) > } > > theta<-rep(-2,4) > fand<-nlogL(theta) > fsim<-negll(theta) > cat("Check fn vals: expm =",fand," simple=",fsim," diff=",fand-fsim,"\n") > > cat("time the function in expm form\n") > tnlogL<-microbenchmark(nlogL(theta), times=100L) > tnlogL > > cat("time the function in simpler form\n") > tnegll<-microbenchmark(negll(theta), times=100L) > tnegll > > ftimes<-data.frame(texpm=tnlogL$time, tsimp=tnegll$time) > # ftimes > > > boxplot(log(ftimes)) > title("Log times in nanoseconds for matrix exponential and simple MIN fn") > > ______________________________________________ > R-devel at r-project.org mailing list > https://stat.ethz.ch/mailman/listinfo/r-devel > > >
John C Nash <nashjc at uottawa.ca> writes:> This message is about a curious difference in timing between two ways of computing the > same function. One uses expm, so is expected to be a bit slower, but "a bit" turned out to > be a factor of >1000. The code is below. We would be grateful if anyone can point out any > egregious bad practice in our code, or enlighten us on why one approach is so much slower > than the other.Looks like A*t in expm(A*t) is a "matrix". 'getMethod("expm","matrix")' coerces it arg to a "Matrix", then calls expm(), whose method coerces its arg to a "dMatrix" and calls expm(), whose method coerces its arg to a 'dgeMatrix' and calls expm(), whose method finally calls '.Call(dgeMatrix_exp, x)' Whew! The time difference between 'expm( diag(10)+1 )' and 'expm( as( diag(10)+1, "dgeMatrix" ))' is a factor of 10 on my box. Dunno 'bout the other factor of 100. Chuck> The problem arose in an activity to develop guidelines for nonlinear > modeling in ecology (at NCEAS, Santa Barbara, with worldwide participants), and we will be > trying to include suggestions of how to prepare problems like this for efficient and > effective solution. The code for nlogL was the "original" from the worker who supplied the > problem. > > Best, > > John Nash > > -------------------------------------------------------------------------------------- > > cat("mineral-timing.R == benchmark MIN functions in R\n") > # J C Nash July 31, 2011 > > require("microbenchmark") > require("numDeriv") > library(Matrix) > library(optimx) > # dat<-read.table('min.dat', skip=3, header=FALSE) > # t<-dat[,1] > t <- c(0.77, 1.69, 2.69, 3.67, 4.69, 5.71, 7.94, 9.67, 11.77, 17.77, > 23.77, 32.77, 40.73, 47.75, 54.90, 62.81, 72.88, 98.77, 125.92, 160.19, > 191.15, 223.78, 287.70, 340.01, 340.95, 342.01) > > # y<-dat[,2] # ?? tidy up > y<- c(1.396, 3.784, 5.948, 7.717, 9.077, 10.100, 11.263, 11.856, 12.251, 12.699, > 12.869, 13.048, 13.222, 13.347, 13.507, 13.628, 13.804, 14.087, 14.185, 14.351, > 14.458, 14.756, 15.262, 15.703, 15.703, 15.703) > > > ones<-rep(1,length(t)) > theta<-c(-2,-2,-2,-2) > > > nlogL<-function(theta){ > k<-exp(theta[1:3]) > sigma<-exp(theta[4]) > A<-rbind( > c(-k[1], k[2]), > c( k[1], -(k[2]+k[3])) > ) > x0<-c(0,100) > sol<-function(t)100-sum(expm(A*t)%*%x0) > pred<-sapply(dat[,1],sol) > -sum(dnorm(dat[,2],mean=pred,sd=sigma, log=TRUE)) > } > > getpred<-function(theta, t){ > k<-exp(theta[1:3]) > sigma<-exp(theta[4]) > A<-rbind( > c(-k[1], k[2]), > c( k[1], -(k[2]+k[3])) > ) > x0<-c(0,100) > sol<-function(tt)100-sum(expm(A*tt)%*%x0) > pred<-sapply(t,sol) > } > > Mpred <- function(theta) { > # WARNING: assumes t global > kvec<-exp(theta[1:3]) > k1<-kvec[1] > k2<-kvec[2] > k3<-kvec[3] > # MIN problem terbuthylazene disappearance > z<-k1+k2+k3 > y<-z*z-4*k1*k3 > l1<-0.5*(-z+sqrt(y)) > l2<-0.5*(-z-sqrt(y)) > val<-100*(1-((k1+k2+l2)*exp(l2*t)-(k1+k2+l1)*exp(l1*t))/(l2-l1)) > } # val should be a vector if t is a vector > > negll <- function(theta){ > # non expm version JN 110731 > pred<-Mpred(theta) > sigma<-exp(theta[4]) > -sum(dnorm(dat[,2],mean=pred,sd=sigma, log=TRUE)) > } > > theta<-rep(-2,4) > fand<-nlogL(theta) > fsim<-negll(theta) > cat("Check fn vals: expm =",fand," simple=",fsim," diff=",fand-fsim,"\n") > > cat("time the function in expm form\n") > tnlogL<-microbenchmark(nlogL(theta), times=100L) > tnlogL > > cat("time the function in simpler form\n") > tnegll<-microbenchmark(negll(theta), times=100L) > tnegll > > ftimes<-data.frame(texpm=tnlogL$time, tsimp=tnegll$time) > # ftimes > > > boxplot(log(ftimes)) > title("Log times in nanoseconds for matrix exponential and simple MIN fn") >-- Charles C. Berry cberry at tajo.ucsd.edu