繁体   English   中英

C ++速度比较迭代器与索引

[英]c++ speed comparison iterator vs index

我目前正在用C ++写一个linalg库,用于教育目的和个人使用。 作为其一部分,我实现了带有自定义行和列迭代器的自定义矩阵类。 虽然提供了很好的功能来使用std :: algorithm和std :: numeric函数,但我对索引和iterator / std :: inner_product方法之间的矩阵乘法进行了速度比较。 结果明显不同:

// used later on for the custom iterator
template<class U>
struct EveryNth {
    bool operator()(const U& ) { return m_count++ % N == 0; }
    EveryNth(std::size_t i) : m_count(0), N(i) {}
    EveryNth(const EveryNth& element) : m_count(0), N(element.N) {}

private:
    int m_count;
    std::size_t N;
};

template<class T, 
         std::size_t rowsize, 
         std::size_t colsize>  
class Matrix
{

private:

    // Data is stored in a MVector, a modified std::vector
    MVector<T> matrix;

    std::size_t row_dim;                  
    std::size_t column_dim;

public:

    // other constructors, this one is for matrix in the computation
    explicit Matrix(MVector<T>&& s): matrix(s), 
                                     row_dim(rowsize), 
                                     column_dim(colsize){
    }    

    // other code...

    typedef boost::filter_iterator<EveryNth<T>, 
                                   typename std::vector<T>::iterator> FilterIter;

    // returns an iterator that skips elements in a range
    // if "to" is to be specified, then from has to be set to a value
    // @ param "j" - j'th column to be requested
    // @ param "from" - starts at the from'th element
    // @ param "to" - goes from the from'th element to the "to'th" element
    FilterIter  begin_col( std::size_t j,
                           std::size_t from = 0, 
                           std::size_t to = rowsize ){
        return boost::make_filter_iterator<EveryNth<T> >(
            EveryNth<T>( cols() ), 
            matrix.Begin() + index( from, j ), 
            matrix.Begin() + index( to, j )
            );
    }

    // specifies then end of the iterator
    // so that the iterator can not "jump" past the last element into undefines behaviour
    FilterIter end_col( std::size_t j, 
                        std::size_t to = rowsize ){
        return boost::make_filter_iterator<EveryNth<T> >(
            EveryNth<T>( cols() ), 
            matrix.Begin() + index( to, j ), 
            matrix.Begin() + index( to, j )
            );
    }

    FilterIter  begin_row( std::size_t i,
                           std::size_t from = 0,
                           std::size_t to = colsize ){
         return boost::make_filter_iterator<EveryNth<T> >(
            EveryNth<T>( 1 ), 
            matrix.Begin() + index( i, from ), 
            matrix.Begin() + index( i, to )
            );
    }

    FilterIter  end_row( std::size_t i,
                         std::size_t to = colsize ){
        return boost::make_filter_iterator<EveryNth<T> >(
            EveryNth<T>( 1 ), 
            matrix.Begin() + index( i, to ), 
            matrix.Begin() + index( i, to )
            );
    }

    // other code...

    // allows to access an element of the matrix by index expressed
    // in terms of rows and columns
    // @ param "r" - r'th row of the matrix
    // @ param "c" - c'th column of the matrix
    std::size_t index(std::size_t r, std::size_t c) const {
        return r*cols()+c; 
    }

    // brackets operator
    // return an elements stored in the matrix
    // @ param "r" - r'th row in the matrix
    // @ param "c" - c'th column in the matrix
    T& operator()(std::size_t r, std::size_t c) { 
        assert(r < rows() && c < matrix.size() / rows());
        return matrix[index(r,c)]; 
    }

    const T& operator()(std::size_t r, std::size_t c) const {
        assert(r < rows() && c < matrix.size() / rows()); 
        return matrix[index(r,c)]; 
    }

    // other code...

    // end of class
};

现在在main函数中运行以下命令:

int main(int argc, char *argv[]){


    Matrix<int, 100, 100> a = Matrix<int, 100, 100>(range<int>(10000));


    std::clock_t begin = clock();
    double b = 0;
    for(std::size_t i = 0; i < a.rows(); i++){
        for (std::size_t j = 0; j < a.cols(); j++) {
                std::inner_product(a.begin_row(i), a.end_row(i), 
                                   a.begin_column(j),0);        
        }
    }

    // double b = 0;
    // for(std::size_t i = 0; i < a.rows(); i++){
    //     for (std::size_t j = 0; j < a.cols(); j++) {
    //         for (std::size_t k = 0; k < a.rows(); k++) {
    //             b += a(i,k)*a(k,j);
    //         }
    //     }
    // }


    std::clock_t end = clock();
    double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    std::cout << elapsed_secs << std::endl;

    std::cout << "--- End of test ---" << std::endl;

    std::cout << std::endl;
    return 0;
}

对于std :: inner_product / iterator方法,它需要:

bash-3.2$ ./main

3.78358
--- End of test ---

对于索引(// out)方法:

bash-3.2$ ./main

0.106173
--- End of test ---

这比迭代器方法快将近40倍。 您是否在代码中看到任何可能大大减慢迭代器计算速度的东西? 我应该提到我尝试了两种方法,它们均能产生正确的结果。

感谢您的想法。

您必须了解的是,矩阵运算非常容易理解,并且编译器非常擅长对矩阵运算涉及的事物进行优化。

考虑C = AB,其中C是MxN,A是MxQ,B是QxN。

double a[M][Q], b[Q][N], c[M][N];
for(unsigned i = 0; i < M; i++){
  for (unsigned j = 0; j < N; j++) {
    double temp = 0.0;
    for (unsigned k = 0; k < Q; k++) {
      temp += a[i][k]*b[k][j];
    }
    c[i][j] = temp;
  }
}

(您不会相信我会多么想在FORTRAN IV中编写以上内容。)

编译器对此进行了观察,并注意到真正发生的是他正在跨步为1的a和c和跨步为Q的b。他消除了下标计算中的乘法并进行了直接索引。

那时,内部循环的形式为:

temp += a[r1] * b[r2];
r1 += 1;
r2 += Q;

而且,您还可以在每个循环中循环(重新)初始化r1和r2。

那是您可以进行直接矩阵乘法的绝对最小计算量。 您只能做这些事情,因为您必须进行那些乘法,加法和索引调整。

您所能做的就是增加开销。

这就是iterator和std :: inner_product()方法的作用:它增加了公吨的开销。

这只是有关底层代码优化的一些其他信息和一般建议。


为了最终确定在低级代码上花费的时间(紧循环和热点),

  1. 您必须能够使用不同的实现策略来实现多个版本的代码,以计算相同的结果。
    • 您将需要广泛的数学和计算知识来做到这一点。
  2. 您必须检查反汇编(机器代码)。
  3. 您还必须在指令级采样分析器下运行代码,以查看机器代码的哪一部分执行得最多(即热点)。
    • 为了收集足够数量的探查器样本,您将需要在紧密循环中运行该代码,数百万或数十亿次。
  4. 您必须比较不同版本的代码(来自不同的实现策略)之间热点的分解。
  5. 根据以上信息,您可以得出以下结论:某些实施策略效率不高(更浪费或冗余)。
    • 如果到达此步骤,则现在可以发布并与他人共享您的发现。

一些可能性:

  1. 使用boost::filter_iterator实现一个跳过每个N元素的迭代器很浪费。 内部实现每次必须递增一。 如果N大,则通过boost::filter_iterator访问下一个元素将成为O(N)操作,这与简单的迭代器算法(即O(1)操作O(1)相反。
  2. 您的boost::filter_iterator实现使用模运算符。 尽管整数除法和模运算在现代CPU上速度很快,但仍不如简单的整数算法快。

简单来说

  • 对于整数和浮点数,增量,减量,加法和减法最快。
  • 乘法和位移比较慢。
  • 除法和取模运算可能会变慢。
  • 最后,浮点三角函数和先验函数,特别是那些需要调用标准数学库函数的函数,将是最慢的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM