基准测试:l应用速度慢一个数量级以上。为什么?

Benchmarked: lapply slower by more than an order of magnitude. Why?

提问人:WishIaskedThisSooner 提问时间:10/28/2023 最后编辑:jay.sfWishIaskedThisSooner 更新时间:10/28/2023 访问量:38

问:

我担心我的代码自由使用(和)可能会慢一个数量级以上。lapplysapply

例如:以以下列表为例,它反映了我的数据。它很小,仅用于说明目的,但请相信我,在处理数百万个数据点时,速度会变慢。lslapply

set.seed(0)
dates <- as.character(seq.Date(as.Date('2023-01-01'), by = 'day', length.out = 365))
m <- sapply(dates, \(d) sample(1:10, 100, replace = TRUE))
rownames(m) <- seq(1001, 1100, 1)
ls <- list()
ls <- sapply(dates, \(d) {ls[[length(ls) + 1]] <- m[, d]; ls})
str(ls[1:3])
List of 3
 $ 2023-01-01: Named int [1:100] 9 4 7 1 2 7 2 3 1 5 ...
  ..- attr(*, "names")= chr [1:100] "1001" "1002" "1003" "1004" ...
 $ 2023-01-02: Named int [1:100] 3 10 3 1 6 6 4 9 5 1 ...
  ..- attr(*, "names")= chr [1:100] "1001" "1002" "1003" "1004" ...
 $ 2023-01-03: Named int [1:100] 1 10 4 9 9 9 9 6 6 4 ...
  ..- attr(*, "names")= chr [1:100] "1001" "1002" "1003" "1004" ...

目标很简单:计算每个元素 x 的 MAX(c(5 - x, 0)),其中有 36,500 个数据点。至少有四种不同的方法可以做到这一点,它们在下面进行了基准测试。ls

library(bench)
bench::mark(a = {
              new <- sapply(dates, \(d) 
                        lapply(rownames(m), \(n) max(c(5 - ls[[d]][n], 0))))
              dimnames(new)[[1]] <- rownames(m)
              new |> lapply(sum) |> unlist() |> sum() # Checksum
              },
            b = {
              new <- sapply(ls, \(x) pmax(5 - x, 0))
              new |> lapply(sum) |> unlist() |> sum() # Checksum
            },
            c = {
              new <- do.call(cbind, ls)
              new <- pmax(5 - new, 0)
              new |> lapply(sum) |> unlist() |> sum() # Checksum
            },
            d = {
              new <- 5 - do.call(cbind, ls)
              new[new < 0] <- 0
              new |> lapply(sum) |> unlist() |> sum() # Checksum
            })
# A tibble: 4 × 13
  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory     time      
  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list>     <list>    
1 a           363.3ms  366.1ms      2.73    1.44MB     2.73     2     2      732ms <dbl>  <Rprofmem> <bench_tm>
2 b            26.1ms   32.2ms     26.5        2MB     9.48    14     5      527ms <dbl>  <Rprofmem> <bench_tm>
3 c            24.1ms   26.4ms     33.5     1.53MB     7.44    18     4      538ms <dbl>  <Rprofmem> <bench_tm>
4 d            23.7ms   26.3ms     21.0      1.6MB     5.25    12     3      572ms <dbl>  <Rprofmem> <bench_tm>

为什么和其余的之间有超过一个数量级的差异?这是一个足够简单的计算。这是否意味着在使用时必须防范隐藏的陷阱?如果是这样,它们是什么?alapply

编辑在 GuedesBF 提供一些建设性的反馈后,我已经清理了我的代码以进行基准测试,这加快了速度,但结果实际上更糟,有近 2 个数量级的差异,哎呀!编写清晰高效的代码是 R 战斗的 90%,而查找教程很困难,这就是我在这里的原因。因此,感谢所有贡献者。

有一点很清楚,混乱的嵌套是问题的一部分,在最终的函数定义中指定了迭代,而不是像现在这样在前面干净地指定。lapply

bench::mark(a = {new <- sapply(ls, \(d)
                           lapply(d, \(x) max(c(5 - x, 0))))
                 new |> unlist() |> sum()}, # Checksum
            b = {new <- sapply(ls, \(x) pmax(5 - x, 0))
                 new |> unlist() |> sum()}, # Checksum
            c = {new <- do.call(cbind, ls)
                 new <- pmax(5 - new, 0)
                 new |> unlist() |> sum()}, # Checksum
            d = {new <- 5 - do.call(cbind, ls)
                 new[new < 0] <- 0
                 new |> unlist() |> sum()}) # Checksum
# A tibble: 4 × 13
  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory     time      
  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list>     <list>    
1 a           60.96ms   62.2ms      16.0    1.16MB     5.34     6     2      375ms <dbl>  <Rprofmem> <bench_tm>
2 b            7.64ms    8.3ms     118.     1.16MB     4.39    54     2      456ms <dbl>  <Rprofmem> <bench_tm>
3 c           621.7µs    818µs    1236.   715.93KB     7.17   517     3      418ms <dbl>  <Rprofmem> <bench_tm>
4 d           627.5µs  803.6µs    1218.   787.13KB     7.35   497     3      408ms <dbl>  <Rprofmem> <bench_tm>

另外,我在想,如果加速只是由于 ,并且没有类似的功能可用,例如 、 等怎么办?好吧,似乎列绑定而不是使用总是更快。以下是一些基准测试结果,其中仍然有近 2 个数量级的差异。pmaxmeansqrtlapplysqrt

bench::mark(a = {new <- sapply(ls, \(d)
                           lapply(d, \(x) sqrt(x)))
                 new |> unlist() |> sum()},
            b = {new <- do.call(cbind, ls)
                 new <- sqrt(new)
                 new |> unlist() |> sum()})
# A tibble: 2 × 13
  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory     time      
  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list>     <list>    
1 a            32.6ms   35.6ms      28.0    1.15MB     5.09    11     2      393ms <dbl>  <Rprofmem> <bench_tm>
2 b           475.9µs    570µs    1713.   430.73KB     6.82   754     3      440ms <dbl>  <Rprofmem> <bench_tm>
r lapply 基准测试

评论


答: 暂无答案