`if`比ifelse更快?

Hao*_*Hao 14 performance benchmarking if-statement r

当我最近重新阅读Hadley的Advanced R时,我注意到他在第6章中说过`if`可以用作一个函数 `if`(i == 1, print("yes"), print("no")) (如果你手头有实体书,那就是第80页)

我们知道这ifelse很慢(ifelse每次都会真正计算它的两个向量吗?它是否很慢?)因为它会评估所有参数.将`if`是一个很好的替代品,if似乎只评估TRUE参数(这只是我的假设)?


更新:基于@Benjamin和@Roman的答案以及@Gregor和其他许多人的评论,ifelse似乎是矢量化计算的更好解决方案.我在这里接受@ Benjamin的回答,因为它提供了更全面的比较和社区健康.但是,这两个答案(以及评论)都值得一读.

Ben*_*min 17

这是一个基于Roman答案的扩展评论,但我需要代码实用程序来解释:

罗马是正确的,if速度快ifelse,但我的印象是速度提升if不是特别有趣,因为它不是可以通过矢量化轻易利用的东西.也就是说,ififelsecond/ test参数长度为1 时才有利.

考虑以下函数,这是一种无可否认的矢量化尝试,if而没有同时评估yesno条件的副作用ifelse.

ifelse2 <- function(test, yes, no){
 result <- rep(NA, length(test))
 for (i in seq_along(test)){
   result[i] <- `if`(test[i], yes[i], no[i])
 }
 result
}

ifelse2a <- function(test, yes, no){
  sapply(seq_along(test),
         function(i) `if`(test[i], yes[i], no[i]))
}

ifelse3 <- function(test, yes, no){
  result <- rep(NA, length(test))
  logic <- test
  result[logic] <- yes[logic]
  result[!logic] <- no[!logic]
  result
}


set.seed(pi)
x <- rnorm(1000)

library(microbenchmark)
microbenchmark(
  standard = ifelse(x < 0, x^2, x),
  modified = ifelse2(x < 0, x^2, x),
  modified_apply = ifelse2a(x < 0, x^2, x),
  third = ifelse3(x < 0, x^2, x),
  fourth = c(x, x^2)[1L + ( x < 0 )],
  fourth_modified = c(x, x^2)[seq_along(x) + length(x) * (x < 0)]
)

Unit: microseconds
            expr     min      lq      mean  median       uq      max neval cld
        standard  52.198  56.011  97.54633  58.357  68.7675 1707.291   100 ab 
        modified  91.787  93.254 131.34023  94.133  98.3850 3601.967   100  b 
  modified_apply 645.146 653.797 718.20309 661.568 676.0840 3703.138   100   c
           third  20.528  22.873  76.29753  25.513  27.4190 3294.350   100 ab 
          fourth  15.249  16.129  19.10237  16.715  20.9675   43.695   100 a  
 fourth_modified  19.061  19.941  22.66834  20.528  22.4335   40.468   100 a 
Run Code Online (Sandbox Code Playgroud)

一些编辑:感谢弗兰克和理查德斯克里文注意到我的缺点.

正如您所看到的,将向量分解为适合传递的if过程是一个耗时的过程,并且最终比运行速度慢ifelse(这可能是没有人为实现我的解决方案而烦恼的原因).

如果你真的急于提高速度,你可以使用ifelse3上面的方法.或者更好的是,弗兰克不那么明显*但是很棒的解决方案.

  • 通过"不太明显",我的意思是,我花了两秒钟才意识到他做了什么.每低于Nicola的评论,请注意,当这个作品只yesno有长度为1,否则你会想坚持ifelse3

  • 嗯,`4th = c("非负","否定")[1L +(x <0)]` (2认同)
  • @RomanTsegelskyi如果你认为"`for`比'sapply`慢得多',那就意味着你没有充分利用R. (2认同)
  • @RomanTsegelskyi在许多情况下,`sapply`可能比`for`循环慢,因为它调用`simplify2array`.另一方面,`lapply`和`vapply`比循环快_generally_.尽管许多人认为,"*apply"实际上并不比循环快得多. (2认同)

Rom*_*kyi 9

if是一个通过.Primitive接口调用的原始(ifelse编译)函数,而是R字节码,所以看起来if会更快.运行一些快速基准测试

> microbenchmark(`if`(TRUE, "a", "b"), ifelse(TRUE, "a", "b"))
Unit: nanoseconds
                   expr  min   lq    mean median     uq   max neval cld
 if (TRUE) "a" else "b"   46   54  372.59   60.0   68.0 30007   100  a 
 ifelse(TRUE, "a", "b") 1212 1327 1581.62 1442.5 1617.5 11743   100   b

> microbenchmark(`if`(FALSE, "a", "b"), ifelse(FALSE, "a", "b"))
Unit: nanoseconds
                    expr  min   lq    mean median   uq   max neval cld
 if (FALSE) "a" else "b"   47   55   91.64   61.5   73  2550   100  a 
 ifelse(FALSE, "a", "b") 1256 1346 1688.78 1460.0 1677 17260   100   b
Run Code Online (Sandbox Code Playgroud)

似乎如果不考虑实际分支中的代码,if至少要快20倍ifelse.但请注意,这并未考虑正在测试的表达式的复杂性以及可能的优化.

更新:请注意,此快速基准代表了ifvs的一个非常简化且有些偏见的用例ifelse(正如评论中所指出的).虽然它是正确的,但它代表了ifelse用例,因为本杰明的答案似乎提供了更公平的比较.

  • 好奇心很好; 危险在于R初学者阅读这个并思考"而不是`ifelse`我应该总是使用`if`因为它更快",而不理解用例的差异. (5认同)
  • @Frank,为什么一切都必须有用?只是好奇才发生了什么?:) (4认同)
  • 好奇心杀死了这只猫. (4认同)
  • 这似乎也不足为奇 - 当然`if`比评估单个条件的速度要快于`ifelse`,当只涉及两个数字时,`+`和`-`将比`sum`和`diff`快. (4认同)