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

时间:2014-10-11 12:14:24

标签: c++ performance iterator

我目前正在用c ++编写一个linalg库,用于教育目的和个人用途。作为其中的一部分,我实现了一个自定义矩阵类与自定义行和列迭代器。虽然提供了非常好的功能来使用std :: algorithm和std :: numeric函数,但我对index和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倍。您是否在代码中看到任何可能减慢迭代器计算的内容?我应该提一下,我尝试了两种方法,并产生了正确的结果。

感谢您的想法。

2 个答案:

答案 0 :(得分:2)

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

考虑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中写这些内容有多么诱惑。)

编译器对此进行了研究,并注意到实际发生的事情是他正在通过a和c走一步,步幅为1和b,步长为Q.他消除了下标计算中的乘法并进行了直接索引

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

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

你周围有循环来(重新)初始化每次传递的r1和r2。

这是您可以进行的直接矩阵乘法的绝对最小计算。你不能做到这一点,因为你必须做那些乘法和加法以及索引调整。

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

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

答案 1 :(得分:2)

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


最后确定在低级代码(紧密循环和热点)中花费的时间,

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

  6. 一些可能性:

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

    3. 简单来说,

      • 对于整数和浮点,递增,递减,加法和减法都是最快的。
      • 乘法和位移稍慢。
      • 分区和模数操作会打得更慢。
      • 最后,浮点三角函数和超越函数,尤其是需要调用标准数学函数函数的函数,将是最慢的。