#r #performance #range #max #min
#r #Производительность #диапазон #макс #min
Вопрос:
Я наткнулся на range
функцию R. Это, безусловно, полезный инструмент и делает код более читаемым, но его скорость можно удвоить, заменив его простым однострочным включением 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 resu<
}
и это дает следующие тесты:
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
Комментарии:
1. Из
range.default
видно, что она выполняет несколько дополнительных проверок перед вызовомc(min(x), max(x))
самой себя. Она не оптимизирована по скорости. Это просто удобная функция. Кажется маловероятным, что эти миллисекундные различия могут быть источником узкого места в производительности.2. Привет, вот как вы просматриваете исходный код
range.default
:getAnywhere(range.default)
3. Определение функции также должно отображаться, если вы просто запустите строку
range.default
4. Спасибо за ответы. Это просто хороший пример того, что некоторые знания о данных, с которыми вы работаете, полезны, поскольку вы можете избежать проверок, которые не нужны.
5. У меня нет представления о R, но есть предположение: Внутренние
x[i]<minValue
иx[i]>maxValue
проверки выдадут результатtrue
только для первых нескольких элементов данных. После этого будут выдаваться только «выбросы»true
. Итак, мне было бы интересно увидеть сравнение этих функций на основе данных, которые являются 1. постоянными, 2. возрастающими, 3. убывающими (да, мы можем услышать слова «ошибка прогнозирования ветвления», пробирающиеся здесь …)
Ответ №1:
Вот исходный код для 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))
вызовом самой себя. Она не оптимизирована по скорости. Это просто удобная функция. Кажется маловероятным, что эти миллисекундные различия могут быть источником узкого места в производительности.