Skip to content

Commit

Permalink
Depend on R 4.0.0 (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
hadley authored Aug 22, 2024
1 parent bb2a93e commit be1deb1
Show file tree
Hide file tree
Showing 8 changed files with 17 additions and 36 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ License: MIT + file LICENSE
URL: https://profvis.r-lib.org, https://github.com/r-lib/profvis
BugReports: https://github.com/r-lib/profvis/issues
Depends:
R (>= 3.0)
R (>= 4.0)
Imports:
htmlwidgets (>= 0.3.2),
rlang (>= 0.4.9),
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# profvis (development version)

* profvis now relies on R 4.0.0.
* `profvis()` now uses doubles instead of integers (#114).
* The version of jQuery bundled in profvis has been upgraded to 3.7.1 (@hedsnz, #139).
* profvis no longer requires purrr or stringr, and no longer suggests ggplot2, devtools, knitr, or rmarkdown.
Expand Down
7 changes: 3 additions & 4 deletions R/profvis.R
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@
#' `torture = steps` helps prevent this, by making R trigger garbage
#' collection after every `torture` memory allocation step.
#' @param simplify Whether to simplify the profiles by removing
#' intervening frames caused by lazy evaluation. This only has an
#' effect on R 4.0. See the `filter.callframes` argument of
#' [Rprof()].
#' intervening frames caused by lazy evaluation. Equivalent to the
#' `filter.callframes` argument to [Rprof()].
#' @param rerun If `TRUE`, `Rprof()` is run again with `expr` until a
#' profile is actually produced. This is useful for the cases where
#' `expr` returns too quickly, before R had time to sample a
Expand Down Expand Up @@ -170,7 +169,7 @@ profvis <- function(expr = NULL,
gc.profiling = TRUE,
memory.profiling = TRUE,
event = if (has_event()) timing,
filter.callframes = if (has_simplify()) simplify
filter.callframes = simplify
))

on.exit(Rprof(NULL), add = TRUE)
Expand Down
18 changes: 4 additions & 14 deletions R/rprof.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,18 @@ rprof_lines <- function(expr,

prof_file <- tempfile("profvis-snapshot", fileext = ".prof")
on.exit(unlink(prof_file), add = TRUE)

if (has_simplify()) {
args <- list(filter.callframes = filter.callframes)
} else {
args <- NULL
}
on.exit(Rprof(NULL), add = TRUE)

while (TRUE) {
env_bind_lazy(current_env(), do = !!expr, .eval_env = env)

gc()
inject(Rprof(
Rprof(
prof_file,
...,
interval = interval,
!!!args
))
filter.callframes = filter.callframes
)

do
Rprof(NULL)
Expand All @@ -65,7 +59,7 @@ re_srcref <- "\\d+#\\d+"
re_srcref_opt <- sprintf(" (%s )?", re_srcref)

rprof_current_suffix <- function(env, simplify, ...) {
if (simplify && getRversion() >= "4.0.3") {
if (simplify) {
# We need to call the suffix routine from the caller frame. We
# inline a closure in the call so we can refer to here despite
# evaluating in a foreign environment. Evaluation is done through
Expand Down Expand Up @@ -136,10 +130,6 @@ gsub_srcref_as_wildcards <- function(lines) {

utils::globalVariables("do")

has_simplify <- function() {
getRversion() >= "4.0.3"
}

has_event <- function() {
getRversion() >= "4.4.0"
}
5 changes: 2 additions & 3 deletions man/profvis.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions tests/testthat/test-rprof.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
test_that("`rprof_lines()` collects profiles", {
skip_on_cran()
skip_on_covr()

f <- function() pause(TEST_PAUSE_TIME)

out <- rprof_lines(f(), rerun = "pause")
Expand All @@ -14,7 +14,6 @@ test_that("`rprof_lines()` collects profiles", {
test_that("`filter.callframes` filters out intervening frames", {
skip_on_cran()
skip_on_covr()
skip_if_not(has_simplify())

# Chains of calls are kept
f <- function() g()
Expand All @@ -30,7 +29,7 @@ test_that("`filter.callframes` filters out intervening frames", {
test_that("stack is correctly stripped even with metadata profiling", {
skip_on_cran()
skip_on_covr()

f <- function() pause(TEST_PAUSE_TIME)
zap <- function(lines) modal_value0(zap_trailing_space(zap_srcref(zap_meta_data(lines))))

Expand All @@ -56,7 +55,7 @@ test_that("stack is correctly stripped even with metadata profiling", {
test_that("`pause()` does not include .Call() when `line.profiling` is set", {
skip_on_cran()
skip_on_covr()

f <- function() pause(TEST_PAUSE_TIME)

# `pause()` should appear first on the line
Expand Down
9 changes: 2 additions & 7 deletions vignettes/articles/examples.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Finally, many of the flame graph cells contain the entire expression from line 6
This profiling data tells us that much of the time is spent in `$` and `$<-`. Maybe avoiding these functions entirely will speed things up. To do that, instead of operating on data frame columns, we can operate on temporary vectors. As it turns out, writing a function that takes a vector as input and returns a vector as output is not only convenient; it provides a natural way of creating temporary variables so that we can avoid calling `$` and `$<-` in a loop.

```{r ex2_csum}
x <- runif(5e5)
profvis({
csum <- function(x) {
if (length(x) < 2) return(x)
Expand All @@ -157,7 +159,6 @@ profvis({
}
sum
}
x <- runif(5e5)
x$sum <- csum(x)
})
```
Expand All @@ -171,12 +172,6 @@ It may appear that no functions are called from line 7, but that's not quite tru

The code panel shows that there is still a large amount of memory being allocated in the `csum` function. In the flame graph. you probably have noticed the gray blocks labeled `<GC>`. These represent times where R is doing *garbage collection* -- that is, when it is freeing (deallocating) chunks of memory that were allocated but no longer needed. If R is spending a lot of time freeing memory, that suggests that R is also spending a lot of time allocating memory. This is another common source of slowness in R code.

> **Note:** Before R 3.4, growing a vector in a loop was expensive -- each time the vector grew by one, R would copy the vector to a new block of memory, with the new item at the end. However, as of R 3.4, R tries to put vectors in a memory location with room to grow, and if there exists is only one reference to the vector, it can grow in place, except in the uncommon case where there's no more room to grow. (If there are two references to the vector, then R must make a copy.) This means that growing a vector is a fast operation, as long as there's only one reference to it.
>
> Although growing a vector is now fast, growing a **data frame** is still an expensive operation and results in copying all of the data.


### Example 3 - Profiling a Shiny Application

In addition to R code, you can also profile [Shiny](http://shiny.rstudio.com) applications. To do this, simply execute the `runApp()` command inside of `profvis`. For instance, we can run one of shiny's built-in examples using the `runExample` command (which is a wrapper for `runApp`).
Expand Down
4 changes: 1 addition & 3 deletions vignettes/articles/faq.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ profvis({
})
```

Similarly, in versions of R before 3.3.0, functions that are accessed with `::` or `$` will also appear as `<Anonymous>`. The form `package::function()` is a common way to explicitly use a namespace to find a function. The form `x$fun()` is a common way to call functions that are contained in a list, environment, reference class, or R6 object. As of R 3.3.0, these will display as `package::function`, or `x$fun`.

Those are equivalent to `` `::`(package, function)`` and `` `$`(x, "fun")``, respectively. These calls return anonymous functions, and so R's internal profiling code labels these as `<Anonymous>`. If you want labels in the profiler to have a different label, you can assign the value to a temporary variable (like `adder2` above), and then invoke that.

Finally, if a function is passed to `lapply`, it will be show up as `FUN` in the flame graph. If we inspect the source code for `lapply`, it's clear why: when a function is passed to `lapply`, the name used for the function inside of `lapply` is `FUN`.
Expand Down Expand Up @@ -156,7 +154,7 @@ profvis({
readRDS("cmpfun-profile1.rds")
```

As of R 3.4.0, R attempts to compile functions when they are first ran to byte code. On subsequent function calls, instead of reinterpreting the body of the function, R executes the saved and compiled byte code. Typically, this results in faster execution times on later function calls. For example, let's profile `csum` a second time in the same R session:
R attempts to compile functions when they are first ran to byte code. On subsequent function calls, instead of reinterpreting the body of the function, R executes the saved and compiled byte code. Typically, this results in faster execution times on later function calls. For example, let's profile `csum` a second time in the same R session:

```{r echo=FALSE}
readRDS("cmpfun-profile2.rds")
Expand Down

0 comments on commit be1deb1

Please sign in to comment.