【发布时间】:2016-04-13 11:33:42
【问题描述】:
这个问题是关于 C++ 优化技术的。我有一个大尺寸的矩阵向量乘法,并希望减少运行时间。我知道有专门的线性代数库,但我实际上想了解一些关于底层处理器特性的知识。到目前为止,我正在使用 \O2 (Microsoft) 进行编译,并让编译器确认乘法的内部循环是矢量化的。
示例代码为:
#include <stdio.h>
#include <ctime>
#include <iostream>
#define VEC_LENGTH 64
#define ITERATIONS 4000000
void gen_vector_matrix_multiplication(double *vec_result, double *vec_a, double *matrix_B, unsigned int cols_B, unsigned int rows_B)
{
// initialise result vector
for (unsigned int i = 0; i < rows_B; i++)
{
vec_result[i] = 0;
}
// perform multiplication
for (unsigned int j = 0; j < cols_B; j++)
{
const double entry = vec_a[j];
const int col = j*rows_B;
for (unsigned int i = 0; i < rows_B; i++)
{
vec_result[i] += entry * matrix_B[i + col];
}
}
}
int main()
{
double *vec_a = new double[VEC_LENGTH];
double *vec_result = new double[VEC_LENGTH];
double *matrix_B = new double[VEC_LENGTH*VEC_LENGTH];
// start clock
clock_t begin = clock();
// this outer loop is just for test purposes so that the timing becomes meaningful
for (unsigned int i = 0; i < ITERATIONS; i++)
{
gen_vector_matrix_multiplication(vec_result, vec_a, matrix_B, VEC_LENGTH, VEC_LENGTH);
}
// stop clock
double elapsed_time = static_cast<double>(clock() - begin) / CLOCKS_PER_SEC;
std::cout << elapsed_time/(VEC_LENGTH*VEC_LENGTH) << std::endl;
delete[] vec_a;
delete[] vec_result;
delete[] matrix_B;
return 1;
}
多次执行乘法以获得对运行时间的可靠估计。我已经测量了许多不同向量长度的运行时间(在这个例子中只有一个元素N,这是向量的长度,同时定义了矩阵的大小NxN)和将测量的运行时间标准化为元素的数量。
您可以看到,对于足够小的N,每个操作的运行时间是恒定的。但是,在N=512 之上,运行时会跳起来。蓝色和红色数据点之间的区别在于处理器的负载。如果示例程序几乎是单独运行,则运行时间由蓝点给出,而当其他核心忙时,时间由红点表示。
我现在有几个与此相关的问题。
- 我是否正确假设
N=512和N=1024之间的跳转与我的处理器(Ivy Bridge i5-3570)的 L3 缓存大小(应该为 6MB)有关?512*512*8byte大约等于 2MB,1024*1024*8byte大约是 8MB。所以矩阵不再适合缓存,因此从 RAM 中获取数据是执行时间较长的原因? - 运行时间稳定增长超过此阈值的原因是什么?
- 对于繁忙和空闲处理器的曲线在阈值以上如此不同的原因有什么想法吗?
- 在优化此乘法例程以使用
N>1024操作时,接下来的逻辑步骤是什么?
我很想听听你的想法。谢谢!
【问题讨论】:
-
@tobi303 你确定复杂性吗?这是向量矩阵乘法而不是矩阵矩阵。只有 N*N 次操作。
-
ups,抱歉一定错过了这条信息;)
-
这可能会鼓励优化器将 vector_a 和 matrix_b 声明为 const double*。如果您使用的是 C,我建议您也使用“restrict”;这不是 C++,尽管一些编译器——例如 gcc——将它的一种形式实现为扩展。
-
@dmuir 感谢您的建议。我刚试过,但时间基本相同。
-
奇怪的是,如果您的循环计数器已签名,这只会使用 gcc/clang 自动矢量化。使用无符号循环计数器,
i + col可能会换行,从而使负载不连续? icc13 会自动矢量化您的原始文件,但 gcc 和 clang 只执行像mulsd这样的标量双操作。 here's my version that does autovectorize。请注意,-ffast-math不需要它进行矢量化,因为 vector*matrix 陷入了在结果向量上循环多次或从矩阵进行跨步加载的尴尬位置。概率。不过,不值得倒置。
标签: c++ performance optimization matrix