为什么在乘法之前转置矩阵会导致很大的加速
Why does transposing matrix before multiplication results in great speed-up
我听说在乘法之前转置矩阵会因为缓存局部性而大大加快运算速度。所以我写了一个简单的C++程序来测试它与行主排序(编译需要 C++11 和 boost)。
结果令人惊讶:7.43秒对0.94秒。但我不明白为什么它会加速。事实上,在第二个版本(第一个转置)中,乘法代码通过 stride-1 模式访问数据,并且比第一个具有更好的局部性。但是,要转置矩阵 B,也必须非顺序地访问数据,并且还会导致大量缓存未命中。分配内存和复制数据的开销也应该是不可忽视的。那么,为什么第二个版本会大大加快代码速度呢?
#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;
}
乘法涉及的访问次数比转置多得多,因此它主导了执行时间。
只需查看 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)
...
有了额外的嵌套,第二个显然要多得多。
相关文章:
- 转置矩阵:交换元素不会更改值
- 使用 Eigen 3 库编写一个带有转置作为参数的函数
- 犰狳(C++)中的快速阵列置换(广义张量转置)
- 为什么转置这个 std::vector<std::vector<std::string> > 这么慢?
- 转置结构容器
- 我的转置矩阵代码有什么问题?
- 数组的转置和乘法
- 在C++中使用矢量转置 2D 矩阵
- 特征:块转置
- MKL矩形矩阵Inplace转置:不使用多个核心
- 并行转置不同的矩阵
- 关于次级对角线的转置(翻转)矩阵
- 输出是从您输入的矩阵中打印出矩阵的转置,但我的代码只是打印出您输入的第一个矩阵
- 如何在阵列火中避免翻转和转置的memcpy?
- CUDA矩阵与共享内存转置
- C 阵列的复合物共轭转置
- 转置期间的动态内存分配
- 转置的一维矢量的平均矢量
- 为什么在乘法之前转置矩阵会导致很大的加速
- 特征:如何加速a += coeffs * coeffs.转置()