[英]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()方法的作用:它增加了公吨的开销。
这只是有关底层代码优化的一些其他信息和一般建议。
为了最终确定在低级代码上花费的时间(紧循环和热点),
一些可能性:
boost::filter_iterator
实现一个跳过每个N元素的迭代器很浪费。 内部实现每次必须递增一。 如果N大,则通过boost::filter_iterator
访问下一个元素将成为O(N)
操作,这与简单的迭代器算法(即O(1)
操作O(1)
相反。 boost::filter_iterator
实现使用模运算符。 尽管整数除法和模运算在现代CPU上速度很快,但仍不如简单的整数算法快。 简单来说
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.