为什么 range() 函数比 min 和 max 的组合慢?
Why is the range() function slower than a combination of min and max?
我发现了 R 的 range
函数。它无疑是一个有用的工具,可以使代码更具可读性,但是通过用包括 min
和 max
.
的简单单行代码替换它可以使它的速度加倍
我做了一些基准测试,范围函数的 'bad' 性能让我感到惊讶。为了比较,我编写了一个名为 range2
的函数,它使用 min 和 max(见代码)。除了速度之外,如果一个简单的单行代码可以胜过这个功能,还有什么理由存在这个功能,这也很容易阅读?
require(microbenchmark)
range2 <- function(x) c(min(x),max(x))
n <- 1000000
x <- rnorm(n)
microbenchmark(range(x), range2(x))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# range(x) 4.696101 4.734751 5.321603 4.796301 4.814751 23.0646 100 b
#range2(x) 2.477602 2.516101 2.542540 2.535051 2.544052 3.7636 100 a
n <- 10000000
x <- rnorm(n)
microbenchmark(range(x), range2(x))
# Unit: milliseconds
# expr min lq mean median uq max neval cld
# range(x) 47.3246 47.9498 58.27992 55.25795 61.98205 146.5100 100 b
#range2(x) 24.7063 25.5021 25.59192 25.55245 25.63515 27.1088 100 a
可以肯定的是,这不是第一个想要摆脱的瓶颈,因为我们讨论的是具有 10,000,000 个条目的向量上的毫秒数,但我预计 range
会更快。我的简单直觉是:
range
遍历数据一次并同时搜索最小值和最大值,而我的 range2
函数遍历数据两次:一次查找最小值和一次找到最大值。
也许有人可以提供一些有关实施的背景信息。也许原因是 min
和 max
是用 C 实现的,而 range
不是?
补充:
我已经和我的一个朋友谈过这个问题,他只是通过以下方式在 C++ 中实现了这个函数,从而使它更快:
#include <Rcpp.h>
#include <float.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector range3(NumericVector x) {
int xs = x.size();
double minValue = FLT_MAX;
double maxValue = FLT_MIN;
for (int i =0; i < xs; i++) {
if (x[i] < minValue) minValue = x[i];
if (x[i] > maxValue) maxValue = x[i];
}
Rcpp::NumericVector result(2);
result[0] = minValue;
result[1] = maxValue;
return result;
}
这给出了以下基准:
n <- 10000000
x <- rnorm(n)
microbenchmark(range(x), range2(x) ,range3(x))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# range(x) 47.8583 48.30355 58.12575 55.3135 62.10295 149.9648 100 c
# range2(x) 24.8211 25.53615 25.90920 25.6176 25.79175 42.4659 100 b
# range3(x) 13.2458 13.30385 13.47175 13.3797 13.65410 14.3487 100 a
这是 range.default
(运行 R 3.6.1)
的来源
> range.default
function (..., na.rm = FALSE, finite = FALSE)
{
x <- c(..., recursive = TRUE)
if (is.numeric(x)) {
if (finite)
x <- x[is.finite(x)]
else if (na.rm)
x <- x[!is.na(x)]
c(min(x), max(x))
}
else {
if (finite)
na.rm <- TRUE
c(min(x, na.rm = na.rm), max(x, na.rm = na.rm))
}
}
您可以看到它在调用 c(min(x), max(x))
之前做了一些额外的检查。它没有针对速度进行优化。这只是一个用户友好的功能。这些毫秒差异似乎不太可能成为性能瓶颈的根源。
我发现了 R 的 range
函数。它无疑是一个有用的工具,可以使代码更具可读性,但是通过用包括 min
和 max
.
我做了一些基准测试,范围函数的 'bad' 性能让我感到惊讶。为了比较,我编写了一个名为 range2
的函数,它使用 min 和 max(见代码)。除了速度之外,如果一个简单的单行代码可以胜过这个功能,还有什么理由存在这个功能,这也很容易阅读?
require(microbenchmark)
range2 <- function(x) c(min(x),max(x))
n <- 1000000
x <- rnorm(n)
microbenchmark(range(x), range2(x))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# range(x) 4.696101 4.734751 5.321603 4.796301 4.814751 23.0646 100 b
#range2(x) 2.477602 2.516101 2.542540 2.535051 2.544052 3.7636 100 a
n <- 10000000
x <- rnorm(n)
microbenchmark(range(x), range2(x))
# Unit: milliseconds
# expr min lq mean median uq max neval cld
# range(x) 47.3246 47.9498 58.27992 55.25795 61.98205 146.5100 100 b
#range2(x) 24.7063 25.5021 25.59192 25.55245 25.63515 27.1088 100 a
可以肯定的是,这不是第一个想要摆脱的瓶颈,因为我们讨论的是具有 10,000,000 个条目的向量上的毫秒数,但我预计 range
会更快。我的简单直觉是:
range
遍历数据一次并同时搜索最小值和最大值,而我的 range2
函数遍历数据两次:一次查找最小值和一次找到最大值。
也许有人可以提供一些有关实施的背景信息。也许原因是 min
和 max
是用 C 实现的,而 range
不是?
补充: 我已经和我的一个朋友谈过这个问题,他只是通过以下方式在 C++ 中实现了这个函数,从而使它更快:
#include <Rcpp.h>
#include <float.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector range3(NumericVector x) {
int xs = x.size();
double minValue = FLT_MAX;
double maxValue = FLT_MIN;
for (int i =0; i < xs; i++) {
if (x[i] < minValue) minValue = x[i];
if (x[i] > maxValue) maxValue = x[i];
}
Rcpp::NumericVector result(2);
result[0] = minValue;
result[1] = maxValue;
return result;
}
这给出了以下基准:
n <- 10000000
x <- rnorm(n)
microbenchmark(range(x), range2(x) ,range3(x))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# range(x) 47.8583 48.30355 58.12575 55.3135 62.10295 149.9648 100 c
# range2(x) 24.8211 25.53615 25.90920 25.6176 25.79175 42.4659 100 b
# range3(x) 13.2458 13.30385 13.47175 13.3797 13.65410 14.3487 100 a
这是 range.default
(运行 R 3.6.1)
> range.default
function (..., na.rm = FALSE, finite = FALSE)
{
x <- c(..., recursive = TRUE)
if (is.numeric(x)) {
if (finite)
x <- x[is.finite(x)]
else if (na.rm)
x <- x[!is.na(x)]
c(min(x), max(x))
}
else {
if (finite)
na.rm <- TRUE
c(min(x, na.rm = na.rm), max(x, na.rm = na.rm))
}
}
您可以看到它在调用 c(min(x), max(x))
之前做了一些额外的检查。它没有针对速度进行优化。这只是一个用户友好的功能。这些毫秒差异似乎不太可能成为性能瓶颈的根源。