lapply vs for loop - Performance R

Fed*_*sso 20 performance r lapply

人们常说,人们应该更喜欢lapplyfor循环.有一些例外,例如Hadley Wickham在他的Advance R书中指出.

(http://adv-r.had.co.nz/Functionals.html)(修改到位,递归等).以下是这种情况之一.

仅仅为了学习,我试图以功能形式重写感知器算法,以便对相对性能进行基准测试.来源(https://rpubs.com/FaiHas/197581).

这是代码.

# prepare input
data(iris)
irissubdf <- iris[1:100, c(1, 3, 5)]
names(irissubdf) <- c("sepal", "petal", "species")
head(irissubdf)
irissubdf$y <- 1
irissubdf[irissubdf[, 3] == "setosa", 4] <- -1
x <- irissubdf[, c(1, 2)]
y <- irissubdf[, 4]

# perceptron function with for
perceptron <- function(x, y, eta, niter) {

  # initialize weight vector
  weight <- rep(0, dim(x)[2] + 1)
  errors <- rep(0, niter)


  # loop over number of epochs niter
  for (jj in 1:niter) {

    # loop through training data set
    for (ii in 1:length(y)) {

      # Predict binary label using Heaviside activation
      # function
      z <- sum(weight[2:length(weight)] * as.numeric(x[ii, 
        ])) + weight[1]
      if (z < 0) {
        ypred <- -1
      } else {
        ypred <- 1
      }

      # Change weight - the formula doesn't do anything
      # if the predicted value is correct
      weightdiff <- eta * (y[ii] - ypred) * c(1, 
        as.numeric(x[ii, ]))
      weight <- weight + weightdiff

      # Update error function
      if ((y[ii] - ypred) != 0) {
        errors[jj] <- errors[jj] + 1
      }

    }
  }

  # weight to decide between the two species

  return(errors)
}

err <- perceptron(x, y, 1, 10)

### my rewriting in functional form auxiliary
### function
faux <- function(x, weight, y, eta) {
  err <- 0
  z <- sum(weight[2:length(weight)] * as.numeric(x)) + 
    weight[1]
  if (z < 0) {
    ypred <- -1
  } else {
    ypred <- 1
  }

  # Change weight - the formula doesn't do anything
  # if the predicted value is correct
  weightdiff <- eta * (y - ypred) * c(1, as.numeric(x))
  weight <<- weight + weightdiff

  # Update error function
  if ((y - ypred) != 0) {
    err <- 1
  }
  err
}

weight <- rep(0, 3)
weightdiff <- rep(0, 3)

f <- function() {
  t <- replicate(10, sum(unlist(lapply(seq_along(irissubdf$y), 
    function(i) {
      faux(irissubdf[i, 1:2], weight, irissubdf$y[i], 
        1)
    }))))
  weight <<- rep(0, 3)
  t
}
Run Code Online (Sandbox Code Playgroud)

由于上述问题,我没想到会有任何持续的改进.但是当我看到使用lapply和使用时的急剧恶化时,我真的很惊讶replicate.

我使用库中的microbenchmark函数获得了这个结果microbenchmark

可能是什么原因?可能是一些内存泄漏?

                                                      expr       min         lq       mean     median         uq
                                                        f() 48670.878 50600.7200 52767.6871 51746.2530 53541.2440
  perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10)  4184.131  4437.2990  4686.7506  4532.6655  4751.4795
 perceptronC(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10)    95.793   104.2045   123.7735   116.6065   140.5545
        max neval
 109715.673   100
   6513.684   100
    264.858   100
Run Code Online (Sandbox Code Playgroud)

第一个函数是lapply/ replicatefunction

第二个是带for循环的函数

第三个是在相同的功能C++使用Rcpp

根据Roland的功能分析.我不确定我能以正确的方式解释它.在我看来,大部分时间用于子集化 功能分析

Jor*_*eys 39

首先,这是一个已经很久被揭穿的神话,for循环比任何慢lapply.forR中的循环已经变得更加高效,目前至少和它一样快lapply.

也就是说,你必须重新考虑你在lapply这里的使用.您的实现需要分配到全局环境,因为您的代码要求您在循环期间更新权重.这是不考虑的正当理由lapply.

lapply是一种你应该用于副作用(或没有副作用)的功能.该函数lapply自动将结果组合在一个列表中,并且不会影响您使用的环境,这与for循环相反.同样的道理replicate.另见这个问题:

R是否适用于家庭而不是语法糖?

您的lapply解决方案速度慢得多的原因是因为您使用它的方式会产生更多的开销.

  • replicate除了sapply内部之外别无其他,所以你实际上要结合sapplylapply实现你的双循环.sapply产生额外的开销,因为它必须测试结果是否可以简化.所以for循环实际上比使用更快replicate.
  • 在您的lapply匿名函数中,您必须为每个观察访问x和y的数据帧.这意味着 - 在你的for循环中相反 - 例如,$每次都必须调用该函数.
  • 由于您使用这些高端的功能,你的"lapply"需要解决49个功能相比,你for的解决方案,只要求26的这些额外功能lapply的解决方案包括像函数的调用match,structure,[[,names,%in%,sys.call,duplicated,...所有功能for循环不需要,因为那个不执行任何这些检查.

如果你想看到这个额外的开销从何而来,看的内部代码replicate,unlist,sapplysimplify2array.

您可以使用以下代码来更好地了解失去性能的位置lapply.逐行运行!

Rprof(interval = 0.0001)
f()
Rprof(NULL)
fprof <- summaryRprof()$by.self

Rprof(interval = 0.0001)
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 
Rprof(NULL)
perprof <- summaryRprof()$by.self

fprof$Fun <- rownames(fprof)
perprof$Fun <- rownames(perprof)

Selftime <- merge(fprof, perprof,
                  all = TRUE,
                  by = 'Fun',
                  suffixes = c(".lapply",".for"))

sum(!is.na(Selftime$self.time.lapply))
sum(!is.na(Selftime$self.time.for))
Selftime[order(Selftime$self.time.lapply, decreasing = TRUE),
         c("Fun","self.time.lapply","self.time.for")]

Selftime[is.na(Selftime$self.time.for),]
Run Code Online (Sandbox Code Playgroud)

  • 我对这个答案中声称的揭穿任何参考资料非常感兴趣。您能在这里提供一些吗? (6认同)

Don*_*nen 13

还有更多关于何时使用forlapply哪种“性能”更好的问题。有时速度很重要,有时记忆很重要。更复杂的是,时间复杂度可能不是您所期望的 - 也就是说,可以在不同的范围内观察到不同的行为,从而使任何笼统的陈述(例如“快于”或“至少一样快”)无效。最后,一个经常被忽视的性能指标是思想到代码,不成熟的优化yada yada。

\n

也就是说,在R 简介中,作者暗示了一些性能问题:

\n
\n

警告:R 代码中使用 for() 循环的频率远低于编译语言中的频率。采用 \xe2\x80\x98整个对象\xe2\x80\x99 视图的代码在 R 中可能会更清晰、更快。

\n
\n

给定类似的用例输入输出,不考虑用户偏好,其中一个明显优于另一个吗?

\n
基准 - 斐波那契数列
\n

我比较了计算 1 到N 个 斐波那契数的方法(受到benchmarkme包的启发),避开第二个圆圈并确保每种方法的输入和输出相同。另外还有四种方法可以火上浇油——矢量化方法 和purrr::map,以及*apply变体vapplysapply

\n
fib <- function(x, ...){\n  x <- 1:x ; phi = 1.6180339887498949 ; v = \\() vector("integer", length(x))\n  bench::mark(\n    vector = {\n      y=v(); y = ((rep(phi, length(x))^x) - ((-rep(phi, length(x)))^-x)) / sqrt(5); y},\n    lapply = {\n      y=v(); y = unlist(lapply(x, \\(.) (phi^. - (-phi)^(-.)) / sqrt(5)), use.names = F); y},\n    loop = {\n      y=v(); `for`(i, x, {y[i] = (phi^i - (-phi)^(-i)) / sqrt(5)}); y},\n    sapply = {\n      y=v(); y = sapply(x, \\(.) (phi^. - (-phi)^(-.)) / sqrt(5)); y},\n    vapply = {\n      y=v(); y = vapply(x, \\(.) (phi^. - (-phi)^(-.)) / sqrt(5), 1); y},\n    map = {\n      y=v(); y <- purrr::map_dbl(x, ~ (phi^. - (-phi)^(-.))/sqrt(5)); y\n    }, ..., check = T\n  )[c(1:9)]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

以下是按中位时间排名的性能比较。

\n
lapply(list(3e2, 3e3, 3e4, 3e5, 3e6, 3e7), fib) # n iterations specified separately\nN = 300\n  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time\n1 vector       38.8us   40.9us    21812.    8.44KB     0     1000     0     45.8ms\n2 vapply        500us    545us     1653.    3.61KB     1.65   999     1      604ms\n3 sapply        518us    556us     1725.   12.48KB     0     1000     0      580ms\n4 lapply      513.4us  612.8us     1620.       6KB     8.14   995     5    614.2ms\n5 loop        549.9us  633.6us     1455.    3.61KB     8.78   994     6    683.3ms\n6 map         649.6us  754.6us     1312.    3.61KB     9.25   993     7    756.9ms\n\nN = 3000\n1 vector      769.7us  781.5us     1257.    82.3KB     1.26   999     1   794.83ms\n2 vapply       5.38ms   5.58ms      173.    35.2KB     0.697  996     4      5.74s\n3 sapply       5.59ms   5.83ms      166.   114.3KB     0.666  996     4      6.01s\n4 loop         5.38ms   5.91ms      167.    35.2KB     8.78   950    50      5.69s\n5 lapply       5.24ms   6.49ms      156.    58.7KB     8.73   947    53      6.07s\n6 map          6.11ms   6.63ms      148.    35.2KB     9.13   942    58      6.35s\n\nN = 30 000\n1 vector       10.7ms   10.9ms      90.9     821KB    0.918   297     3      3.27s\n2 vapply       57.3ms   60.1ms      16.4  351.66KB    0.741   287    13      17.5s\n3 loop         59.2ms   60.7ms      15.9     352KB    16.7    146   154      9.21s\n4 sapply       59.6ms   62.1ms      15.7    1.05MB    0.713   287    13      18.2s\n5 lapply       57.3ms   67.6ms      15.1     586KB    20.5    127   173      8.43s\n6 map          66.7ms   69.1ms      14.4     352KB    21.6    120   180      8.35s\n\nN = 300 000\n1 vector        190ms    193ms      5.14    8.01MB    0.206   100     4     19.45s\n2 loop          693ms    713ms      1.40    3.43MB    7.43    100   532      1.19m\n3 map           766ms    790ms      1.26    3.43MB    7.53    100   598      1.32m\n4 vapply        633ms    814ms      1.33    3.43MB    0.851   100    39      45.8s\n5 lapply        685ms    966ms      1.06    5.72MB    9.13    100   864      1.58m\n6 sapply        694ms    813ms      1.27   12.01MB    0.810   100    39      48.1s\n\nN = 3 000 000\n1 vector        3.17s    3.21s    0.312     80.1MB    0.249   20    16       1.07m\n2 vapply        8.22s    8.37s    0.118     34.3MB    4.97    20    845      2.83m\n3 loop           8.3s    8.42s    0.119     34.3MB    4.35    20    733      2.81m\n4 map           9.09s    9.17s    0.109     34.3MB    4.91    20    903      3.07m\n5 lapply       10.42s   11.09s    0.0901    57.2MB    4.10    20    909       3.7m\n6 sapply       10.43s   11.28s    0.0862   112.1MB    3.58    20    830      3.87m\n\nN = 30 000 000\n1 vector        44.8s   45.94s   0.0214      801MB   0.00854  10      4       7.8m\n2 vapply        1.56m     1.6m   0.0104      343MB   0.883    10    850        16m\n3 loop          1.56m    1.62m   0.00977     343MB   0.366    10    374      17.1m\n4 map           1.72m    1.74m   0.00959     343MB   1.23     10   1279      17.4m\n5 lapply        2.15m    2.22m   0.00748     572MB   0.422    10    565      22.3m\n6 sapply        2.05m    2.25m   0.00747    1.03GB   0.405    10    542      22.3m\n\n# Intel i5-8300H CPU @ 2.30GHz / R version 4.1.1 / purrr 0.3.4\n
Run Code Online (Sandbox Code Playgroud)\n

forlapply方法的性能类似,但lapply在内存方面更加贪婪,并且当输入大小增加时(对于此任务)速度会慢一些。请注意,purrr::map内存使用量相当于for-loop,优于lapply,本身就是一个有争议的话题*apply*然而,当使用适当的时候,这里的vapply性能是相似的。但这种选择可能会对内存使用产生很大影响,sapply其内存效率明显低于vapply.

\n

深入了解这些方法可以揭示不同性能的原因。for循环执行许多类型检查,从而产生一些开销。另一方面,lapply受到有缺陷的语言设计的影响,其中惰性求值或使用 Promise 是有代价的,源代码确认了 和 的X参数FUN.Internal(lapply)Promise。

\n

矢量化方法速度很快,并且可能比fororlapply方法更理想。请注意,与其他方法相比,矢量化方法的增长如何不规则。然而,矢量化代码的美观可能是一个问题:您更喜欢调试哪种方法?

\n

总的来说,我认为普通 R 用户不应该考虑在lapply或之间进行选择。for坚持最容易编写、思考和调试的内容,或者不太容易(无声?)出错的内容。性能上的损失可能会被节省的写作时间所抵消。对于性能关键型应用程序,请确保使用不同的输入大小运行一些测试并正确地对代码进行分块。

\n