Armadillo 中的新 `find_finite` 函数比循环慢 3.5 倍?

Posted

技术标签:

【中文标题】Armadillo 中的新 `find_finite` 函数比循环慢 3.5 倍?【英文标题】:New `find_finite` function in Armadillo 3.5x slower than loop? 【发布时间】:2014-05-07 12:41:49 【问题描述】:

Armadillo 4.300 中的新 find_finitefind_nonfinite 函数是很好的补充!在我使用Rcpp 的测试中,与标准循环相比,它们慢了大约 2.5 倍。下面是一些代码,用于计算与 R 的 na.rm=TRUE 选项相对应的按大小写删除的总和和平均值。 R 的性能基准测试表明,与循环相比,第一个版本(sum_armamean_arma)大约快 3.5 倍。我做的一切正确吗?有什么方法可以提高性能?

C++ 代码

#include <numeric>
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]


// [[Rcpp::export]]
double sum_arma1(arma::mat& X) 
    double sum = 0;
    for (int i = 0; i < X.size(); ++i) 
        if (arma::is_finite(X(i)))
            sum += X(i);
    
    return sum;

// [[Rcpp::export]]
double sum_arma2(arma::mat& X) 
    return arma::sum(X.elem(arma::find_finite(X)));


// [[Rcpp::export]]
double mean_arma1(arma::mat& X) 
    double sum = 0;
    int n = 0;
    for (int i = 0; i < X.size(); ++i) 
        if (arma::is_finite(X(i))) 
            sum += X(i);
            n += 1;
        
    
    return sum/n;

// [[Rcpp::export]]
double mean_arma2(arma::mat& X) 
    return arma::mean(X.elem(arma::find_finite(X)));

R 的基准测试结果

# data
X = matrix(rnorm(1e6),1000,1000)
X[sample(1:1000,100),sample(1:1000,100)] = NA
# equal?
all.equal(sum(X, na.rm=TRUE),sum_arma1(X))
all.equal(sum(X, na.rm=TRUE),sum_arma2(X))
all.equal(mean(X, na.rm=TRUE),mean_arma1(X))
all.equal(mean(X, na.rm=TRUE),mean_arma2(X))

# benchmark
benchmark(
    sum(X, na.rm=TRUE),
    sum_arma1(X),
    sum_arma2(X),
    replications=100)

#                   test replications elapsed relative user.self sys.self
# 2         sum_arma1(X)          100   0.259    1.000     0.259    0.001
# 3         sum_arma2(X)          100   1.035    3.996     0.750    0.293
# 1 sum(X, na.rm = TRUE)          100   0.491    1.896     0.492    0.003

benchmark(
    mean(X, na.rm=TRUE),
    mean_arma1(X),
    mean_arma2(X),
    replications=100)

#                   test replications elapsed relative user.self sys.self
# 2         mean_arma1(X)          100   0.252     1.00     0.253    0.001
# 3         mean_arma2(X)          100   0.819     3.25     0.620    0.206
# 1 mean(X, na.rm = TRUE)          100   7.440    29.52     7.120    0.373

【问题讨论】:

【参考方案1】:

通用函数find_finite()find_nonfinite() 总是比专门的求和循环慢。 find_finite() 不是专门为求和而设计的,而是针对一般情况下,嗯,找到有限值的索引。您如何处理这些索引取决于您,您已选择将它们用作.elem() 函数的输入。

在代码arma::sum(X.elem(arma::find_finite(X))) 中,函数find_finite() 必须通过X,寻找有限值,并将有限值的结果索引存储在临时向量中。 .elem() 成员函数然后查看find_finite() 生成的向量并创建另一个仅包含有限值的向量。反过来,.elem() 生成的向量随后被sum() 使用。

C++ 允许抽象,因此您的代码非常紧凑,但有时您必须为此类抽象付费。通用函数总是比专用循环慢。

但是,对于加法、乘法等算术函数,Armadillo 将尝试通过使用智能延迟运算框架(基于模板表达式)来避免生成临时向量/矩阵,该框架会排队并组合多个执行它们之前的操作。这减少了临时对象的生成。

延迟运算的实现相当复杂,这就是为什么它主要针对最重要的算术函数来完成。但是,Armadillo 在其他一些情况下也有它,例如,find(X &gt; 123) 将避免为 X &gt; 123 生成临时值。

【讨论】:

以上是关于Armadillo 中的新 `find_finite` 函数比循环慢 3.5 倍?的主要内容,如果未能解决你的问题,请参考以下文章

Armadillo - 从列向量中的值填充矩阵

Armadillo C ++中的元素方式向量或矩阵乘法

从 Rcpp Armadillo 中的 sp_mat 访问维度名称

链接错误:Armadillo 库中的“未定义对‘pthread_atfork’的引用”

在 Mac OS X 上安装 C++ Armadillo 库

Armadillo / Xcode:“仅返回类型不同的函数不能被重载”错误无处不在