我发现了R语言的range
函数,它无疑是一个有用的工具,可以使代码更易读,但是通过用包含min
和max
的简单一行代码来替换它,可以将其速度提升一倍。
我进行了一些基准测试,发现range函数的性能不佳令我感到惊讶。为了比较,我编写了一个名为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
中可以看出,在调用c(min(x), max(x))
之前,它会进行一些额外的检查。它并不是针对速度进行优化的,而是一个用户友好的函数。这些毫秒级别的差异似乎不太可能成为性能瓶颈的来源。 - MrFlickgetAnywhere(range.default)
查看range.default
的源代码。 - Cetttrange.default
иҝҷдёҖиЎҢд»Јз Ғж—¶жҳҫзӨәгҖӮ - IceCreamToucanx[i]<minValue
和x[i]>maxValue
检查仅对前几个数据元素返回true
。之后,只有“异常值”才会返回true
。因此,我很想看一下这些函数在以下数据基础上的比较情况:1.恒定数据,2.升序数据,3.降序数据(是的,我们可以听到“分支预测失败”这些词语在这里悄悄蔓延...) - Marco13