Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/159.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 与整数相比,R中的算术运算速度更快。什么';发生什么事了? 我在把一些主要使用数字数据(即双倍)的代码转换成整数,并做了一个快速的基准,看看我获得了多大的效率。_C++_C_R_Performance_Rcpp - Fatal编程技术网

C++ 与整数相比,R中的算术运算速度更快。什么';发生什么事了? 我在把一些主要使用数字数据(即双倍)的代码转换成整数,并做了一个快速的基准,看看我获得了多大的效率。

C++ 与整数相比,R中的算术运算速度更快。什么';发生什么事了? 我在把一些主要使用数字数据(即双倍)的代码转换成整数,并做了一个快速的基准,看看我获得了多大的效率。,c++,c,r,performance,rcpp,C++,C,R,Performance,Rcpp,令我惊讶的是,它慢了。。。大约20%。我认为我做错了什么,但原始代码只是对中等大小的向量进行了一些基本的算术运算,所以我知道不是这样。也许我的环境一团糟?我重新开始,结果还是一样的。。。整数的效率较低 这就开始了一系列的测试,并潜入兔子洞。这是我的第一次测试。我们用基数R求一百万个元素的和。请注意,R版本3.5.0的计时有很大不同,而V3.5.1的计时大致相同(仍然不是人们所期望的): 我知道C中没有函数重载或向量,但你明白我的意思了。我的信念是,最终,一堆相同类型的元素被添加到相同类型的元素中

令我惊讶的是,它慢了。。。大约20%。我认为我做错了什么,但原始代码只是对中等大小的向量进行了一些基本的算术运算,所以我知道不是这样。也许我的环境一团糟?我重新开始,结果还是一样的。。。整数的效率较低

这就开始了一系列的测试,并潜入兔子洞。这是我的第一次测试。我们用基数R求一百万个元素的和。请注意,R版本
3.5.0
的计时有很大不同,而V3.5.1的计时大致相同(仍然不是人们所期望的):

我知道
C
中没有函数重载或向量,但你明白我的意思了。我的信念是,最终,一堆相同类型的元素被添加到相同类型的元素中,并最终返回。在
Rcpp
中,我们将有如下内容:

template <typename typeReturn, typename typeRcpp>
typeReturn sumRcpp(typeRcpp x) {
    typeReturn mySum = 0;
    unsigned long int mySize = x.size();

    for (std::size_t i = 0; i < mySize; ++i)
        mySum += x[i];

    return mySum;
}

// [[Rcpp::export]]
SEXP mySumTest(SEXP Rx) {
    switch(TYPEOF(Rx)) {
        case INTSXP: {
            IntegerVector xInt = as<IntegerVector>(Rx);
            int resInt = sumRcpp<int>(xInt);
            return wrap(resInt);
        }
        case REALSXP: {
            NumericVector xNum = as<NumericVector>(Rx);
            double resDbl = sumRcpp<double>(xNum);
            return wrap(resDbl);
        }
        default: {
            Rcpp::stop("Only integers and numerics are supported");   
        }
    }
}
二进制运算符 这让我进一步思考。也许正是围绕着
standardGeneric
的复杂性使得不同的数据类型表现得异常。那么,让我们跳过所有的jazz,直接进入二进制运算符(
+,-,*,/,%%

在每种情况下,我们都可以看到,在数字数据类型上,算法的速度要快40%-70%。真正奇怪的是,当操作的两个向量相同时,我们得到了更大的差异:

microbenchmark(intPlus = int1e6 + int1e6, 
               dblPlus = dbl1e6 + dbl1e6, times = 1000)
Unit: microseconds
   expr      min       lq     mean   median       uq      max neval
intPlus 2522.774 3148.464 3894.723 3304.189 3531.310 73354.97  1000
dblPlus  977.892 1703.865 2710.602 1767.801 1886.648 77738.47  1000

microbenchmark(intSub = int1e6 - int1e6,
               dblSub = dbl1e6 - dbl1e6, times = 1000)
Unit: microseconds
  expr      min       lq     mean   median       uq      max neval
intSub 2236.225 2854.068 3467.062 2994.091 3214.953 11202.06  1000
dblSub  893.819 1658.032 2789.087 1730.981 1873.899 74034.62  1000

microbenchmark(intMult = int1e6 * int1e6, 
               dblMult = dbl1e6 * dbl1e6, times = 1000)
Unit: microseconds
   expr      min       lq     mean   median       uq      max neval
intMult 2852.285 3476.700 4222.726 3658.599 3926.264 78026.18  1000
dblMult  973.640 1679.887 2638.551 1754.488 1875.058 10866.52  1000

microbenchmark(intDiv = int1e6 %/% int1e6,
               dblDiv = dbl1e6 / dbl1e6, times = 1000)
Unit: microseconds
  expr      min       lq     mean   median       uq      max neval
intDiv 2879.608 3355.015 4052.564 3531.762 3797.715 11781.39  1000
dblDiv  945.519 1627.203 2706.435 1701.512 1829.869 72215.51  1000

unique(c(class(int1e6 + int1e6), class(int1e6 - int1e6),
         class(int1e6 * int1e6), class(int1e6 %/% int1e6)))
# [1] "integer"

unique(c(class(dbl1e6 + dbl1e6), class(dbl1e6 - dbl1e6),
         class(dbl1e6 * dbl1e6), class(dbl1e6 / dbl1e6)))
# [1] "numeric"
这几乎是每种操作员类型的100%增长

以R为底的常规for循环如何

funInt <- function(v) {
    mySumInt <- 0L
    for (element in v)
        mySumInt <- mySumInt + element
    mySumInt
}

funDbl <- function(v) {
    mySumDbl <- 0
    for (element in v)
        mySumDbl <- mySumDbl + element
    mySumDbl
}

microbenchmark(funInt(int1e6), funDbl(dbl1e6))
Unit: milliseconds
          expr      min       lq     mean   median       uq      max neval
funInt(int1e6) 25.44143 25.75075 26.81548 26.09486 27.60330 32.29436   100
funDbl(dbl1e6) 24.48309 24.82219 25.68922 25.13742 26.49816 29.36190   100

class(funInt(int1e6))
# [1] "integer"
class(funDbl(dbl1e6))
# [1] "numeric"
评论中的“随机猜测”真的很好!功能 似乎是
算术.c
中的起点。首先,对于标量,我们看到的情况是:例如,使用标准
+
。例如,对于
INTSXP
有一个to,它确实检查整数溢出:

static R_INLINE int R_integer_plus(int x, int y, Rboolean *pnaflag)
{
    if (x == NA_INTEGER || y == NA_INTEGER)
    return NA_INTEGER;

    if (((y > 0) && (x > (R_INT_MAX - y))) ||
    ((y < 0) && (x < (R_INT_MIN - y)))) {
    if (pnaflag != NULL)
        *pnaflag = TRUE;
    return NA_INTEGER;
    }
    return x + y;
}
结果:

Unit: microseconds
              expr      min        lq     mean    median       uq       max neval
int1e6 + int1e6two 1999.698 2046.2025 2232.785 2061.7625 2126.970  5461.816  1000
            sumInt  812.560  846.1215 1128.826  861.9305  892.089 44723.313  1000
    sumIntOverflow 1664.351 1690.2455 1901.472 1702.6100 1760.218  4868.182  1000
dbl1e6 + dbl1e6two 1444.172 1501.9100 1997.924 1526.0695 1641.103 47277.955  1000
           sumReal 1459.224 1505.2715 1887.869 1530.5995 1675.594  5124.468  1000

将溢出检查引入C++代码会显著降低性能。即使它没有标准的

+
那么糟糕。因此,如果您知道您的整数“表现良好”,您可以通过直接转到C/C++跳过R的错误检查来获得相当多的性能。这让我想起了一个类似的结论。由R执行的错误检查可能代价高昂

对于向量相同的情况,我得到以下基准测试结果:

Unit: microseconds
           expr      min       lq     mean    median       uq       max neval
int1e6 + int1e6 1761.285 2000.720 2191.541 2011.5710 2029.528 47397.029  1000
         sumInt  648.151  761.787 1002.662  767.9885  780.129 46673.632  1000
 sumIntOverflow 1408.109 1647.926 1835.325 1655.6705 1670.495 44958.840  1000
dbl1e6 + dbl1e6 1081.079 1119.923 1443.582 1137.8360 1173.807 44469.509  1000
        sumReal 1076.791 1118.538 1456.917 1137.2025 1250.850  5141.558  1000

双打(R和C++)的性能显著提高。整数的性能也有一些提高,但不如双精度整数那么容易捕获。

我不确定应该应用哪些标签。如果有人认为标签应该不同,请随时更新。可能是因为它需要检查整数溢出?(随机猜测)@F.Privé,这是一个很好的观点。。。我将做一些挖掘,看看这是否是caseOn CentOS 7,R 3.5.0,我得到的第一个基准的int中位数为675,double中位数为1022。@F.Privé,我刚刚添加了我的会话信息。你测试过二进制运算符了吗?我想知道是否可以定义一个“不安全的”
%%++
运算符(不知道调度是否会消耗任何收益)。这是一项非常出色的工作。有一件事仍然让我有点困扰,那就是当向量相同时,我们可以通过二进制运算符获得更高的效率(数值运算更是如此)。如果非要我猜的话,缓存中发生了一些事情,可能还有一些分支预测。。。。继续可能,在进行了10000个左右相同的元素添加之后,CPU可以猜测这将在剩余的计算中执行,而不需要第二个元素的硬负载。继续我的猜测,我们失去了额外的整数检查的优势,因为它必须离开父函数来检查整数溢出。@JosephWood我在使用相同的向量时添加了我的基准测试结果。double的性能提高比int(R和C++)更为显著。我不知道这是从哪里来的。顺便说一句,
R\u integer\u plus
等可能由编译器内联。
microbenchmark(intPlus = int1e6 + int1e6, 
               dblPlus = dbl1e6 + dbl1e6, times = 1000)
Unit: microseconds
   expr      min       lq     mean   median       uq      max neval
intPlus 2522.774 3148.464 3894.723 3304.189 3531.310 73354.97  1000
dblPlus  977.892 1703.865 2710.602 1767.801 1886.648 77738.47  1000

microbenchmark(intSub = int1e6 - int1e6,
               dblSub = dbl1e6 - dbl1e6, times = 1000)
Unit: microseconds
  expr      min       lq     mean   median       uq      max neval
intSub 2236.225 2854.068 3467.062 2994.091 3214.953 11202.06  1000
dblSub  893.819 1658.032 2789.087 1730.981 1873.899 74034.62  1000

microbenchmark(intMult = int1e6 * int1e6, 
               dblMult = dbl1e6 * dbl1e6, times = 1000)
Unit: microseconds
   expr      min       lq     mean   median       uq      max neval
intMult 2852.285 3476.700 4222.726 3658.599 3926.264 78026.18  1000
dblMult  973.640 1679.887 2638.551 1754.488 1875.058 10866.52  1000

microbenchmark(intDiv = int1e6 %/% int1e6,
               dblDiv = dbl1e6 / dbl1e6, times = 1000)
Unit: microseconds
  expr      min       lq     mean   median       uq      max neval
intDiv 2879.608 3355.015 4052.564 3531.762 3797.715 11781.39  1000
dblDiv  945.519 1627.203 2706.435 1701.512 1829.869 72215.51  1000

unique(c(class(int1e6 + int1e6), class(int1e6 - int1e6),
         class(int1e6 * int1e6), class(int1e6 %/% int1e6)))
# [1] "integer"

unique(c(class(dbl1e6 + dbl1e6), class(dbl1e6 - dbl1e6),
         class(dbl1e6 * dbl1e6), class(dbl1e6 / dbl1e6)))
# [1] "numeric"
funInt <- function(v) {
    mySumInt <- 0L
    for (element in v)
        mySumInt <- mySumInt + element
    mySumInt
}

funDbl <- function(v) {
    mySumDbl <- 0
    for (element in v)
        mySumDbl <- mySumDbl + element
    mySumDbl
}

microbenchmark(funInt(int1e6), funDbl(dbl1e6))
Unit: milliseconds
          expr      min       lq     mean   median       uq      max neval
funInt(int1e6) 25.44143 25.75075 26.81548 26.09486 27.60330 32.29436   100
funDbl(dbl1e6) 24.48309 24.82219 25.68922 25.13742 26.49816 29.36190   100

class(funInt(int1e6))
# [1] "integer"
class(funDbl(dbl1e6))
# [1] "numeric"
sessionInfo()
R version 3.5.1 (2018-07-02)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.6
static R_INLINE int R_integer_plus(int x, int y, Rboolean *pnaflag)
{
    if (x == NA_INTEGER || y == NA_INTEGER)
    return NA_INTEGER;

    if (((y > 0) && (x > (R_INT_MAX - y))) ||
    ((y < 0) && (x < (R_INT_MIN - y)))) {
    if (pnaflag != NULL)
        *pnaflag = TRUE;
    return NA_INTEGER;
    }
    return x + y;
}
#include <Rcpp.h>
// [[Rcpp::plugins(cpp11)]]
#include <cstdint>
using namespace Rcpp;

// [[Rcpp::export]]
IntegerVector sumInt(IntegerVector a, IntegerVector b) {
  IntegerVector result(no_init(a.size()));
  std::transform(a.begin(), a.end(), b.begin(), result.begin(),
                 [] (int32_t x, int32_t y) {return x + y;});
  return result;
}

// [[Rcpp::export]]
IntegerVector sumIntOverflow(IntegerVector a, IntegerVector b) {
  IntegerVector result(no_init(a.size()));
  std::transform(a.begin(), a.end(), b.begin(), result.begin(),
                 [] (int32_t x, int32_t y) {
    if (x == NA_INTEGER || y == NA_INTEGER)
      return NA_INTEGER;
    if (((y > 0) && (x > (INT32_MAX - y))) ||
        ((y < 0) && (x < (INT32_MIN - y))))
      return NA_INTEGER;
    return x + y;
  });
  return result;
}

// [[Rcpp::export]]
NumericVector sumReal(NumericVector a, NumericVector b) {
  NumericVector result(no_init(a.size()));
  std::transform(a.begin(), a.end(), b.begin(), result.begin(),
                 [] (double x, double y) {return x + y;});
  return result;
}

/*** R
set.seed(123)
int1e6 <- sample(1:10, 1e6, TRUE)
int1e6two <- sample(1:10, 1e6, TRUE)
dbl1e6 <- runif(1e6, 1, 10)
dbl1e6two <- runif(1e6, 1, 10)

microbenchmark::microbenchmark(int1e6 + int1e6two,
                               sumInt(int1e6, int1e6two),
                               sumIntOverflow(int1e6, int1e6two),
                               dbl1e6 + dbl1e6two,
                               sumReal(dbl1e6, dbl1e6two),
                               times = 1000)
*/
Unit: microseconds
              expr      min        lq     mean    median       uq       max neval
int1e6 + int1e6two 1999.698 2046.2025 2232.785 2061.7625 2126.970  5461.816  1000
            sumInt  812.560  846.1215 1128.826  861.9305  892.089 44723.313  1000
    sumIntOverflow 1664.351 1690.2455 1901.472 1702.6100 1760.218  4868.182  1000
dbl1e6 + dbl1e6two 1444.172 1501.9100 1997.924 1526.0695 1641.103 47277.955  1000
           sumReal 1459.224 1505.2715 1887.869 1530.5995 1675.594  5124.468  1000
Unit: microseconds
           expr      min       lq     mean    median       uq       max neval
int1e6 + int1e6 1761.285 2000.720 2191.541 2011.5710 2029.528 47397.029  1000
         sumInt  648.151  761.787 1002.662  767.9885  780.129 46673.632  1000
 sumIntOverflow 1408.109 1647.926 1835.325 1655.6705 1670.495 44958.840  1000
dbl1e6 + dbl1e6 1081.079 1119.923 1443.582 1137.8360 1173.807 44469.509  1000
        sumReal 1076.791 1118.538 1456.917 1137.2025 1250.850  5141.558  1000