C++ 为什么在乘法之前转置矩阵会导致极大的速度提高
我听说在乘法之前转置矩阵会大大加快运算速度,因为缓存的局部性。所以我写了一个简单的C++程序,用行主要顺序来测试它(编译需要C++ 11和Boost)。 结果是惊人的:7.43秒对0.94秒。但我不明白为什么它会加速。事实上,在第二个版本(先转置)中,乘法代码通过stride-1模式访问数据,并且比第一个版本具有更好的局部性。然而,要转置矩阵B,还必须非顺序地访问数据,并导致大量缓存未命中。分配内存和复制数据的开销也应该是不容忽视的。那么为什么第二个版本的代码速度会提高这么多呢C++ 为什么在乘法之前转置矩阵会导致极大的速度提高,c++,optimization,matrix-multiplication,C++,Optimization,Matrix Multiplication,我听说在乘法之前转置矩阵会大大加快运算速度,因为缓存的局部性。所以我写了一个简单的C++程序,用行主要顺序来测试它(编译需要C++ 11和Boost)。 结果是惊人的:7.43秒对0.94秒。但我不明白为什么它会加速。事实上,在第二个版本(先转置)中,乘法代码通过stride-1模式访问数据,并且比第一个版本具有更好的局部性。然而,要转置矩阵B,还必须非顺序地访问数据,并导致大量缓存未命中。分配内存和复制数据的开销也应该是不容忽视的。那么为什么第二个版本的代码速度会提高这么多呢 #include
#include <iostream>
#include <vector>
#include <boost/timer/timer.hpp>
#include <random>
std::vector<int> random_ints(size_t size)
{
std::vector<int> result;
result.reserve(size);
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<int> dist(0, 100);
for (size_t i = 0; i < size; ++i)
result.push_back(dist(engine));
return result;
}
// matrix A: m x n; matrix B: n x p; matrix C: m x n;
std::vector<int> matrix_multiply1(const std::vector<int>& A, const std::vector<int>& B, size_t m, size_t n, size_t p)
{
boost::timer::auto_cpu_timer t;
std::vector<int> C(m * p);
for (size_t i = 0; i < m; ++i)
{
for (size_t j = 0; j < p; ++j)
{
for (size_t k = 0; k < n; ++k)
{
C[i * m + j] += A[i * m + k] * B[k * n + j];
// B is accessed non-sequentially
}
}
}
return C;
}
// matrix A: m x n; matrix B: n x p; matrix C: m x n;
std::vector<int> matrix_multiply2(const std::vector<int>& A, const std::vector<int>& B, size_t m, size_t n, size_t p)
{
boost::timer::auto_cpu_timer t;
std::vector<int> C(m * p), B_transpose(n * p);
// transposing B
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < p; ++j)
{
B_transpose[i + j * p] = B[i * n + j];
// B_transpose is accessed non-sequentially
}
}
// multiplication
for (size_t i = 0; i < m; ++i)
{
for (size_t j = 0; j < p; ++j)
{
for (size_t k = 0; k < n; ++k)
{
C[i * m + j] += A[i * m + k] * B_transpose[k + j * p];
// all sequential access
}
}
}
return C;
}
int main()
{
const size_t size = 1 << 10;
auto A = random_ints(size * size);
auto C = matrix_multiply1(A, A, size, size, size);
std::cout << C.front() << ' ' << C.back() << std::endl; // output part of the result
C = matrix_multiply2(A, A, size, size, size);
std::cout << C.front() << ' ' << C.back() << std::endl; // compare with output of algorithm 1
return 0;
}
#包括
#包括
#包括
#包括
标准::矢量随机输入(大小)
{
std::向量结果;
结果:储备量(大小);
std::随机_装置rd;
标准:mt19937发动机(rd());
标准:均匀分布区(01100);
对于(大小i=0;i const size\u t size=1乘法比转置涉及更多的访问,因此它控制执行时间
只需查看for循环标题,您就可以非常清楚地看到这一点:
// transpose
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < p; ++j)
...
// multiplication
for (size_t i = 0; i < m; ++i)
for (size_t j = 0; j < p; ++j)
for (size_t k = 0; k < n; ++k)
...
//转置
对于(尺寸i=0;i
有了额外的嵌套,第二个显然要做更多的工作。我建议修改代码,交替尝试每种算法并重复三到四次。否则,加速可能是由于缓存预热、分支预测或其他与测试方式有关的因素,而不是代码优化。@DavidSchwartz:我试过你的建议。没什么区别。试着分别对转置和乘法进行计时。这可能会告诉你更多。这是有道理的。但我期待的是CPU缓存的神奇特性,我没有完全理解。