<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/rstudioaddins-part-5-profile-your-code-on-keypress-in-the-background-with-no-dependencies/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/"> Jozef's Rblog</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<div>
<h1>Introduction</h1>
<p>Profiling our code is a very useful tool to determine how well the code performs on different metrics.</p>
<blockquote>
<h4>The addin we will create in this article will let us use a keyboard shortcut to run profiling on R code selected in RStudio without blocking the session or requiring any external packages.</h4>
</blockquote>
<p>Specifically for very simple overview use, it may be beneficial to look at the time needed for a set of expressions to compute, e.g. how fast the code is. Secondly, especially important in case of computing on big datasets in-memory, the amount of memory utilized, e.g. how much RAM was used.</p>
<div>
<img alt="The addin in action" src="https://i2.wp.com/jozefhajnala.gitlab.io/r/img/r105-01-run-profiler.gif?w=456&ssl=1" data-recalc-dims="1">
<p>The addin in action</p>
</div>
</div>
<div>
<h1>Contents</h1>
<ol>
<li><a rel="nofollow" href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/#profiling-options-provided-by-base-r" target="_blank">Profiling options provided by base R</a></li>
<li><a rel="nofollow" href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/#asynchronous-execution-and-communication-of-the-results-with-the-session" target="_blank">Asynchronous execution and communication of the results with the session</a></li>
<li><a rel="nofollow" href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/#results-of-the-profiling" target="_blank">Results of the profiling</a></li>
<li><a rel="nofollow" href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/#the-addin-formalities" target="_blank">The addin formalities</a></li>
<li><a rel="nofollow" href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/#tldr---just-give-me-the-package" target="_blank">TL;DR – Just give me the package</a></li>
<li><a rel="nofollow" href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/#references" target="_blank">References</a></li>
</ol>
</div>
<div>
<h1>Profiling options provided by base R</h1>
<p>Without going into any detail at all, we have 2 very nice options to profile our code with base R:</p>
<ul>
<li><code>base::system.time(expr)</code> – returns CPU and other times that <code>expr</code> used</li>
<li><code>utils::Rprof</code> – can serve as a switch to enable and disable profiling, with a variety of options, saving the results into a file on disk, by default <code>&quot;Rprof.out&quot;</code></li>
</ul>
<p>For the use of our addin, we will utilize the second approach, as we are interested not only in time spent, but also in memory utilization of the profiled expressions.</p>
<p>After finishing the profiling, we will use <code>utils::summaryRprof</code> to summarize the results provided to us by the <code>Rprof</code> functionality mentioned above. To get an overview, we will examine only the total time the selected expressions took to execute and the maximum memory.</p>
<p>The very simplistic implementation can look as follows:</p>
<pre><code>profileExpression &lt;- function(expr) {
  on.exit({
    unlink(&quot;Rprof.out&quot;)
    utils::Rprof(NULL)
  })

  if (!is.expression(expr)) {
    message(&quot;epxr must be an expression in profileExpression()&quot;)
    return(data.frame(
      totalTime = numeric(0),
      maxMemory = numeric(0)
    ))
  }
  gc()
  utils::Rprof(
    memory.profiling = TRUE,
    interval = 0.01,
    append = FALSE
  )
  evalRes &lt;- try(eval(expr), silent = TRUE)
  utils::Rprof(NULL)
  if (inherits(evalRes, &quot;try-error&quot;)) {
    return(data.frame(stringsAsFactors = FALSE,
                      totalTime = &quot;EvalError&quot;,
                      maxMemory = &quot;EvalError&quot;
    ))
  }
  res &lt;- utils::summaryRprof(memory = &quot;both&quot;)
  data.frame(
    totalTime = max(res[[&quot;by.total&quot;]][, 1L]),
    maxMemory = max(res[[&quot;by.total&quot;]][, 5L])
  )
}</code></pre>
<p>Since we maybe be interested in more than one execution of the expressions to be profiled and the profiling will be running in background, a wrapper executing the profiling itself multiple times may come in handy. Except the number of times to execute, which is a very standard argument, we can also attempt to provide a time frame we want to invest into the profiling:</p>
<pre><code>multiProfile &lt;- function(
  expr,
  times = 10L,
  maxtime = getOption(&quot;jhaddins_profiler_maxtime&quot;, default = NULL)
){
  if (!(is.integer(times) || is.integer(maxtime))) {
    message(&quot;Times or maxtime must be integer in multiProfile()&quot;)
    return(data.frame(
      totalTime = numeric(0),
      maxMemory = numeric(0)
    ))
  }

  first &lt;- profileExpression(expr)
  if (!is.null(maxtime)) {
    if (is.numeric(first[[&quot;totalTime&quot;]])) {
      times &lt;- floor(maxtime / first[[&quot;totalTime&quot;]])
    } else {
      message(&quot;Eval failed, cannot compute times from maxtime.&quot;)
      return(first)
    }
  }
  if (times &lt;= 1L) {
    return(first)
  }
  rest &lt;- do.call(
    rbind,
    lapply(rep(list(expr), times - 1L), profileExpression)
  )
  rbind(first, rest)
}</code></pre>
</div>
<div>
<h1>Asynchronous execution and communication of the results with the session</h1>
<p>Since we are only using base R functionality without taking advantage of external packages that would help us execute the profiling asynchronously, we have 3 challenges:</p>
<ol>
<li>Asynchronous execution of the profiling</li>
</ol>
<p>We can take advantage of base R’s convenient interface <code>system2</code>, which allows us to invoke OS commands, with the option to run asynchronously providing <code>wait = FALSE</code> as argument.</p>
<ol start="2">
<li>Communicating the results between our R session and the one running via <code>system2</code></li>
</ol>
<p>To kill two birds with one stone, we can simply use the <code>rstudioapi</code> to navigate to a created file, into which we will later write the profiling results using the asynchronously running process. This way we have the results immediately available within in RStudio and we can keep working conveniently on the tasks at hand. Since our application is very simple, we also avoid complications with communication between the processes for example via sockets.</p>
<ol start="3">
<li>Contents of the workspace</li>
</ol>
<p>When selecting a code chunk to profile in RStudio, it will likely happen very soon that the execution of expressions included in the selected code will rely on the current state of the global environment (aka. workspace). We can therefore make our functionality more convenient by storing the contents of the global environment on disk and loading it before running the profiler in our asynchronous process.</p>
<p>A simple example implementation of the thoughts above it once again presented below. Note that this implementation is very bare-bones and could use much polishing, which may happen sometime after publishing this article:</p>
<pre><code>runProfiler &lt;- function(
  inpContext = rstudioapi::getActiveDocumentContext()
){
  force(inpContext)
  inpString &lt;- inpContext[[&quot;selection&quot;]][[1L]][[&quot;text&quot;]]
  cat(inpString, file = file.path(&quot;~/temp.R&quot;))
  expr &lt;- try(parse(&quot;~/temp.R&quot;), silent = TRUE)
  if (inherits(expr, &quot;try-error&quot;)) {
    message(&quot;Selected text cannot be parsed, cannot profile.&quot;)
    unlink(file.path(&quot;~/temp.R&quot;))
    return(1L)
  }
  save(
    list = ls(all.names = TRUE, envir = .GlobalEnv),
    file = &quot;~/tmp.RData&quot;,
    envir = .GlobalEnv
  )
  script &lt;- paste(sep = &quot;; &quot;,
    &quot;load('~/tmp.RData')&quot;,
    &quot;res &lt;- jhaddins:::multiProfile(parse('~/temp.R'))&quot;,
    &quot;jhaddins:::writeProfileDf(res)&quot;,
    &quot;unlink('~/temp.R')&quot;,
    &quot;unlink('~/tmp.RData')&quot;
  )
  file.create(&quot;~/tmp_prof.txt&quot;)
  rstudioapi::navigateToFile(&quot;~/tmp_prof.txt&quot;)
  system2(
    command = 'Rscript',
    args = c('-e', shQuote(script)),
    wait = FALSE
  )
  message(&quot;Profiler running in the background&quot;)
}</code></pre>
</div>
<div>
<h1>Results of the profiling</h1>
<p>For the use that this simple functionality was developed, the main interest is knowing 2 very simple sets of information – how fast did the expressions execute and how much maximum memory was utilized. This is why the results are extracted and written in an extremely simplistic way, as can be seen below:</p>
<div>
<img alt="“quand il n’y a plus rien à retrancher”" src="https://i0.wp.com/jozefhajnala.gitlab.io/r/img/r105-02-results.png?w=456&ssl=1" data-recalc-dims="1">
<p>“quand il n’y a plus rien à retrancher”</p>
</div>
<p>Based on real-life usage we may still improve the presentation (a bit <img alt="😉" src="https://s.w.org/images/core/emoji/2.4/72x72/1f609.png" class="wp-smiley"> in the future.</p>
</div>
<div>
<h1>The addin formalities</h1>
<p>If you follow this blog for a bit, you can safely skip this part. A few things to make our new addin available and easy to use:</p>
<ol>
<li>Add the addin bindings into <code>inst/addins.dcf</code></li>
</ol>
<pre><code>Name: runProfiler
Description: experimental, runProfiler
Binding: runProfiler
Interactive: false</code></pre>
<ol start="2">
<li>Re-install the package</li>
<li>Assign a keyboard shortcut in the <code>Tools -> Addins -> Browse Addins... -> Keyboard Shortcuts...</code> menu in RStudio:</li>
</ol>
<div>
<img alt="Assigning a keyboard shortcut to use the Addin" src="https://i1.wp.com/jozefhajnala.gitlab.io/r/img/r103-03-key-binding.gif?w=456&ssl=1" data-recalc-dims="1">
<p>Assigning a keyboard shortcut to use the Addin</p>
</div>
</div>
<div>
<h1>TL;DR – Just give me the package</h1>
<ul>
<li><a rel="nofollow" href="https://gitlab.com/jozefhajnala/jhaddins.git" target="_blank"><code>https://gitlab.com/jozefhajnala/jhaddins.git</code></a></li>
</ul>
</div>
<div>
<h1>References</h1>
<ul>
<li><a rel="nofollow" href="https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Profiling-R-code-for-speed" target="_blank">Profiling R code for speed</a> at Writing R Extensions</li>
<li><a rel="nofollow" href="https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Profiling-R-code-for-memory-use" target="_blank">Profiling R code for memory use</a> at Writing R Extensions</li>
<li><a rel="nofollow" href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/system.time.html" target="_blank">system.time help</a></li>
<li><a rel="nofollow" href="https://github.com/rstudio/profvis" target="_blank">Profvis package</a> with useful graphical overviews.</li>
<li><a rel="nofollow" href="https://cran.r-project.org/web/packages/microbenchmark/" target="_blank">Microbenchmark package</a> infrastructure to accurately measure and compare the execution time of R expressions</li>
<li><a rel="nofollow" href="https://stat.ethz.ch/R-manual/R-devel/library/parallel/doc/parallel.pdf" target="_blank">parallel package</a></li>
<li><a rel="nofollow" href="https://github.com/r-lib/callr" target="_blank">callR package</a> – to perform a computation in a separate R process</li>
</ul>
<h2>Did you find the article helpful or interesting? Help others find it by sharing</h2>
<div></div>
</div>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://jozefhajnala.gitlab.io/r/r105-async-profiler/"> Jozef's Rblog</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/n2w4COUwXj4" height="1"></a></div>