在 while 循环中停止 R 中的修改时复制行为

Stop copy-on-modify behavior in R in a while loop

我正在对 R 进行拒绝抽样,需要尽可能高效。这是我的原始代码:

N <- 1e8
x <- rexp(N, 3) + rexp(N, 3)
todo <- runif(N, -1, 1) < cos(3.2*pi*x)

while(any(todo)){
  x[todo] <- rexp(sum(todo), 3) + rexp(sum(todo), 3)
  todo[todo] <- runif(sum(todo), -1, 1) < cos(3.2*pi*x[todo])
}

我在 https://adv-r.hadley.nz/names-values.html 上阅读有关复制和修改的内容,并决定使用 lobstr 包来查找是否有任何相关对象(使用 tracemem() 函数),并且 lo-看哪,逻辑向量 todo 在 while 循环中不断被复制!

如果我做以下测试:

library(lobstr)
N <- 1e8
x <- rexp(N, 3) + rexp(N, 3)
todo <- runif(N, -1, 1) < cos(3.2*pi*x)
cat(tracemem(todo), "\n")

while(any(todo)){
  x[todo] <- rexp(sum(todo), 3) + rexp(sum(todo), 3)
  todo[todo] <- runif(sum(todo), -1, 1) < cos(3.2*pi*x[todo])
}

我得到以下结果(证实了我的担忧):

[1] "0x38f4938"
[1] "0x1959ff30"
[1] "0x38f4938"
[1] "0x173bb6f0"
[1] "0x38f4938"
[1] "0x4caf788"
[1] "0x38f4938"
[1] "0x1a801628"
[1] "0x38f4938"
[1] "0x18f36768"
[1] "0x38f4938"
[1] "0x4e4d478"
[1] "0x38f4938"
[1] "0x195b93d8"
[1] "0x38f4938"
[1] "0x3f59fe0"
[1] "0x38f4938"
[1] "0x45ebf40"
[1] "0x38f4938"
[1] "0x1a42bdd8"
[1] "0x38f4938"
[1] "0x16c72ba0"

有人可以帮我摆脱这种耗时的行为吗?我尝试了以下声明,但没有成功:

  1. todo <- logical(N)

  2. todo <- list(logical(N))

编辑:另外,如果能帮助我提高代码中这个瓶颈的时间效率,我们将不胜感激...

我认为 base R 不可能做到这一点,但您可以使用 data.table 包优化此过程。试试这个,你会看到没有复制(我还做了一些其他的小改动来进一步优化你的代码)

library(data.table)

N <- 1e7; i <- 1:N
dt <- list(x = double(N), todo = logical(N)); setDT(dt)
cat(tracemem(dt), "\n")
cat(tracemem(N), "\n")
cat(tracemem(i), "\n")

while(N > 0L){
  set(dt, i, "x", rexp(N, 3) + rexp(N, 3))
  set(dt, i, "todo", runif(N, -1, 1) < cos(3.2*pi*dt[i]$x))
  N <- length(i <- which(dt$todo))
}

到运行大约需要3秒,时间有点长。我认为还有进一步改进的空间。

system.time({
  N <- 1e7; i <- 1:N
  dt <- list(x = double(N), todo = logical(N)); setDT(dt)
  
  while(N > 0L){
    set(dt, i, "x", rexp(N, 3) + rexp(N, 3))
    set(dt, i, "todo", runif(N, -1, 1) < cos(3.2*pi*dt[i]$x))
    N <- length(i <- which(dt$todo))
  }
})

结果

 user  system elapsed 
 3.26    0.10    3.34