我正在用这个简单的算法进行矩阵乘法。为了更灵活,我使用了包含动态创建数组的matricies对象。
将此解决方案与我的第一个解决方案与静态数组进行比较,速度慢了4倍。我该怎么做才能加快数据访问速度?我不想改变算法。
matrix mult_std(matrix a, matrix b) {
matrix c(a.dim(), false, false);
for (int i = 0; i < a.dim(); i++)
for (int j = 0; j < a.dim(); j++) {
int sum = 0;
for (int k = 0; k < a.dim(); k++)
sum += a(i,k) * b(k,j);
c(i,j) = sum;
}
return c;
}
<小时/> 的修改
k
和j
循环迭代 - &gt;绩效改善dim()
和operator()()
声明为inline
- &gt;绩效改善现在的表现与现在的表现几乎相同。也许应该有更多改进。
但我有另一个问题:我在函数mult_strassen(...)
中出现内存错误。为什么?
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
c99 main.c -o matrix -O3
g++ main.cpp matrix.cpp -o matrix -O3
。
<小时/> 的修改
答案 0 :(得分:26)
说到加速,如果你交换k
和j
循环迭代的顺序,你的函数将更容易缓存:
matrix mult_std(matrix a, matrix b) {
matrix c(a.dim(), false, false);
for (int i = 0; i < a.dim(); i++)
for (int k = 0; k < a.dim(); k++)
for (int j = 0; j < a.dim(); j++) // swapped order
c(i,j) += a(i,k) * b(k,j);
return c;
}
这是因为最内层循环上的k
索引会在每次迭代时导致b
中的缓存未命中。在j
作为最内层索引的情况下,c
和b
都会被连续访问,而a
会保持不变。
答案 1 :(得分:4)
你说你不想修改算法,但这究竟意味着什么?
将循环计数展开为“修改算法”吗?使用SSE / VMX如何在CPU上使用哪些SIMD指令?如何使用某种形式的blocking来改善缓存局部性呢?
如果您不想重新构建的代码,我怀疑您可以做的比您已经做过的更改更多。其他所有内容都会成为对算法进行细微更改的权衡,以实现性能提升。
当然,你仍然应该看一下编译器生成的asm。这将告诉你更多关于如何加快代码的工作。
答案 2 :(得分:3)
确保成员dim()
和operator()()
是内联声明的,并且打开了编译器优化。然后使用-funroll-loops
(在gcc上)等选项进行播放。
a.dim()
有多大?如果矩阵的一行不适合只有几个缓存行,那么你最好使用块访问模式而不是一次完整的行。
答案 3 :(得分:3)
matrix
传递复杂类型 - 使用const引用。dim()
。答案 4 :(得分:1)
通过const引用传递参数以开始于:
matrix mult_std(matrix const& a, matrix const& b) {
为了向您提供更多详细信息,我们需要了解所使用的其他方法的详细信息 要回答为什么原始方法快4倍,我们需要看原始方法。
问题无疑是你的问题,因为此问题已经解决了一百万次。
当提出这类问题时,总是为可编辑的来源提供适当的输入,这样我们就可以构建并运行代码,看看发生了什么。
没有我们猜测的代码。
修复原始C代码中的主要错误(缓冲区溢出)
我已更新代码,以便在公平比较中并排运行测试:
// INCLUDES -------------------------------------------------------------------
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
// DEFINES -------------------------------------------------------------------
// The original problem was here. The MAXDIM was 500. But we were using arrays
// that had a size of 512 in each dimension. This caused a buffer overrun that
// the dim variable and caused it to be reset to 0. The result of this was causing
// the multiplication loop to fall out before it had finished (as the loop was
// controlled by this global variable.
//
// Everything now uses the MAXDIM variable directly.
// This of course gives the C code an advantage as the compiler can optimize the
// loop explicitly for the fixed size arrays and thus unroll loops more efficiently.
#define MAXDIM 512
#define RUNS 10
// MATRIX FUNCTIONS ----------------------------------------------------------
class matrix
{
public:
matrix(int dim)
: dim_(dim)
{
data_ = new int[dim_ * dim_];
}
inline int dim() const {
return dim_;
}
inline int& operator()(unsigned row, unsigned col) {
return data_[dim_*row + col];
}
inline int operator()(unsigned row, unsigned col) const {
return data_[dim_*row + col];
}
private:
int dim_;
int* data_;
};
// ---------------------------------------------------
void random_matrix(int (&matrix)[MAXDIM][MAXDIM]) {
for (int r = 0; r < MAXDIM; r++)
for (int c = 0; c < MAXDIM; c++)
matrix[r][c] = rand() % 100;
}
void random_matrix_class(matrix& matrix) {
for (int r = 0; r < matrix.dim(); r++)
for (int c = 0; c < matrix.dim(); c++)
matrix(r, c) = rand() % 100;
}
template<typename T, typename M>
float run(T f, M const& a, M const& b, M& c)
{
float time = 0;
for (int i = 0; i < RUNS; i++) {
struct timeval start, end;
gettimeofday(&start, NULL);
f(a,b,c);
gettimeofday(&end, NULL);
long s = start.tv_sec * 1000 + start.tv_usec / 1000;
long e = end.tv_sec * 1000 + end.tv_usec / 1000;
time += e - s;
}
return time / RUNS;
}
// SEQ MULTIPLICATION ----------------------------------------------------------
int* mult_seq(int const(&a)[MAXDIM][MAXDIM], int const(&b)[MAXDIM][MAXDIM], int (&z)[MAXDIM][MAXDIM]) {
for (int r = 0; r < MAXDIM; r++) {
for (int c = 0; c < MAXDIM; c++) {
z[r][c] = 0;
for (int i = 0; i < MAXDIM; i++)
z[r][c] += a[r][i] * b[i][c];
}
}
}
void mult_std(matrix const& a, matrix const& b, matrix& z) {
for (int r = 0; r < a.dim(); r++) {
for (int c = 0; c < a.dim(); c++) {
z(r,c) = 0;
for (int i = 0; i < a.dim(); i++)
z(r,c) += a(r,i) * b(i,c);
}
}
}
// MAIN ------------------------------------------------------------------------
using namespace std;
int main(int argc, char* argv[]) {
srand(time(NULL));
int matrix_a[MAXDIM][MAXDIM];
int matrix_b[MAXDIM][MAXDIM];
int matrix_c[MAXDIM][MAXDIM];
random_matrix(matrix_a);
random_matrix(matrix_b);
printf("%d ", MAXDIM);
printf("%f \n", run(mult_seq, matrix_a, matrix_b, matrix_c));
matrix a(MAXDIM);
matrix b(MAXDIM);
matrix c(MAXDIM);
random_matrix_class(a);
random_matrix_class(b);
printf("%d ", MAXDIM);
printf("%f \n", run(mult_std, a, b, c));
return 0;
}
结果现在:
$ g++ t1.cpp
$ ./a.exe
512 1270.900000
512 3308.800000
$ g++ -O3 t1.cpp
$ ./a.exe
512 284.900000
512 622.000000
从中我们可以看出,C代码在完全优化时的速度是C ++代码的两倍。我无法在代码中看到原因。
答案 5 :(得分:1)
这是我对方形浮点矩阵(2D数组)的快速简单乘法算法的实现。它应该比chrisaycock代码快一点,因为它可以节省一些增量。
static void fastMatrixMultiply(const int dim, float* dest, const float* srcA, const float* srcB)
{
memset( dest, 0x0, dim * dim * sizeof(float) );
for( int i = 0; i < dim; i++ ) {
for( int k = 0; k < dim; k++ )
{
const float* a = srcA + i * dim + k;
const float* b = srcB + k * dim;
float* c = dest + i * dim;
float* cMax = c + dim;
while( c < cMax )
{
*c++ += (*a) * (*b++);
}
}
}
}
答案 6 :(得分:0)
我在这里做了一个疯狂的猜测,但如果动态分配矩阵会产生如此巨大的差异,那么问题可能就是碎片。同样,我不知道底层矩阵是如何实现的。
为什么不手动为矩阵分配内存,确保它是连续的,并自己构建指针结构?
此外,dim()方法是否有任何额外的复杂性?我也会内联声明。