6

I'd like to make a basic profiling tool that collects time stamps and produces run times with a note. The only problem is I'm having trouble figuring out how to do this without using global variables. What's the "correct" way to implement the functionality I'm trying to achieve? If R already has this functionality built in, that's awesome, but what I'm really trying to figure out here is how to avoid using global variables and write more robust code.

timeStamps = c()
runTimes = list()

appendRunTimes <- function(note) {
  if(length(timeStamps) < 1) {
    timeStamps <<- Sys.time()
  }
  else {
    timeStamps <<- c(timeStamps, Sys.time())
    diff <- timeStamps[length(timeStamps) ] - timeStamps[length(timeStamps) - 1]
    runTimes <<- c(runTimes,  format(diff))
    names(runTimes)[length(runTimes)] <<-  note
  }

}


appendRunTimes('start')
Sys.sleep(4)
appendRunTimes('test')
flodel
  • 87,577
  • 21
  • 185
  • 223
user1949984
  • 93
  • 1
  • 6
  • 3
    I'd look at the code of `system.time` for ideas.. or use it! You can also look at the code for some of the benchmarking packages (`microbenchmark` for example). – Justin Jan 04 '13 at 22:57
  • In addition to the other suggestions, see "Introduction to R" (http://cran.r-project.org/doc/manuals/R-intro.pdf) section 10.7, page 50 (2.15.2 revision). – Matthew Lundberg Jan 05 '13 at 04:38

2 Answers2

8

Here's your example rewritten using closures:

RTmonitor <- local({
  timeStamps = c()
  runTimes = list()

  list(
    appendRunTimes=function(note) {
      if(length(timeStamps) < 1) {
        timeStamps <<- Sys.time()
      }
      else {
        timeStamps <<- c(timeStamps, Sys.time())
        diff <- timeStamps[length(timeStamps) ] - timeStamps[length(timeStamps) - 1]
        runTimes <<- c(runTimes,  format(diff))
        names(runTimes)[length(runTimes)] <<-  note
      }
    },
    viewRunTimes=function() {
      return(list(timeStamps=timeStamps,runTimes=runTimes))
    })
})


> RTmonitor$appendRunTimes("start")
> RTmonitor$appendRunTimes("test")
> RTmonitor$viewRunTimes()
$timeStamps
[1] "2013-01-04 18:39:12 EST" "2013-01-04 18:39:21 EST"

$runTimes
$runTimes$test
[1] "8.855587 secs"

Observe that the values are stored inside the closure, not in the global environment:

> timeStamps
Error: object 'timeStamps' not found
> runTimes
Error: object 'runTimes' not found
> RTmonitor$timeStamps
NULL
> RTmonitor$runTimes
NULL

More reading on closures and avoiding globals:

Community
  • 1
  • 1
Ari B. Friedman
  • 71,271
  • 35
  • 175
  • 235
4

Here's now to do this using ReferenceClasses:

RTmonitor = setRefClass("RTmonitor",
  fields=list(
    timeStamps="POSIXct",
    runTimes = "list"
    ),
  methods=list(
    appendRunTimes=function(note){
      if(length(timeStamps)==0){
        timeStamps <<- Sys.time()
      }else{
        timeStamps <<- c(timeStamps, Sys.time())
        diff <- timeStamps[length(timeStamps) ] - timeStamps[length(timeStamps) - 1]
        runTimes <<- c(runTimes,  format(diff))
        names(runTimes)[length(runTimes)] <<-  note
      }
    }
    )
  )

Now you've defined a class, instantiate an object and use it:

> r = RTmonitor$new()
> r$appendRunTimes("start")
> r$appendRunTimes("test")
> r
Reference class object of class "RTmonitor"
Field "timeStamps":
[1] "2013-01-05 14:52:25 GMT" "2013-01-05 14:52:31 GMT"
Field "runTimes":
$test
[1] "5.175815 secs"

Very similar to the closure approach, but more formalised. I had to define the timeStamps field as POSIXct, for example. You can also create multiple RTmonitor objects this way and they work independently - you'd have to write a closure constructor that wrapped the closure to do that.

Spacedman
  • 92,590
  • 12
  • 140
  • 224