使用AVX2运行速度较慢的矩阵乘法代码

时间:2016-12-10 11:19:45

标签: c++ c simd avx

我正在学习用AVX编程。因此,我编写了一个简单的程序来乘以大小为4的矩阵。虽然没有编译器优化,AVX版本比非AVX版本略快,通过O3优化,非AVX版本变得几乎是AVX的两倍版。关于如何提高AVX版本性能的任何提示?以下是完整的代码。

#include <immintrin.h>
#include <stdio.h>       
#include <stdlib.h>      

#define MAT_SIZE    4
#define USE_AVX

double A[MAT_SIZE][MAT_SIZE];
double B[MAT_SIZE][MAT_SIZE];
double C[MAT_SIZE][MAT_SIZE];

union {
    double m[4][4];
    __m256d row[4];
} matB;

void init_matrices()
{
    for(int i = 0; i < MAT_SIZE; i++)
        for(int j = 0; j < MAT_SIZE; j++)
        {
            A[i][j] = (float)(i+j);
            B[i][j] = (float)(i+j+1);
            matB.m[i][j] = B[i][j];
        }
}

void print_result()
{
    for(int i = 0; i < MAT_SIZE; i++)
    {
        for(int j = 0; j < MAT_SIZE; j++)
        {
            printf("%.1f\t", C[i][j]);
        }
        printf("\n");
    }
}

void withoutAVX()
{
    for(int row = 0; row < MAT_SIZE; row++)
        for(int col = 0; col < MAT_SIZE; col++)
        {
            float sum = 0;
            for(int e = 0; e < MAT_SIZE; e++)
                sum += A[row][e] * B[e][col];
            C[row][col] = sum;
        }
}

void withAVX()
{
    for(int row = 0; row < 4; row++)
    {
        //calculate_resultant_row(row);
        const double* rowA = (const double*)&A[row];
        __m256d* pr = (__m256d*)(&C[row]);

        *pr = _mm256_mul_pd(_mm256_broadcast_sd(&rowA[0]), matB.row[0]);
        for(int i = 1; i < 4; i++)
            *pr = _mm256_add_pd(*pr, _mm256_mul_pd(_mm256_broadcast_sd(&rowA[i]), 
                matB.row[i]));
    }
}

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

int main() 
{
    init_matrices();

    // start timer
    unsigned long long cycles = rdtsc();
#ifdef USE_AVX
    withAVX();
#else
    withoutAVX();
#endif
    // stop timer
    cycles = rdtsc() - cycles;

    printf("\nTotal time elapsed : %ld\n\n", cycles); 
    print_result();
    return 0;
}

1 个答案:

答案 0 :(得分:3)

如果不确切知道您正在使用哪种编译器和系统,很难确定。您需要检查生成的代码的汇编以确定。以下只是一些可能的原因。

编译器可能生成了额外的加载/存储。这将花费。

来自A的最里面的循环广播元素。因此你有额外的负载。最佳代码仅需要8个负载,4个用于A和B,4个存储在C中。但是,由于您使用了broadcastsd,因此您的代码将导致至少16个额外负载。这些将花费你的计算本身,可能更多。

修改(评论时间过长)

在某些情况下,编译器无法进行智能优化,或者某些时候它太聪明了#34;为了好。最近我甚至需要使用汇编来避免编译器优化,这实际上会导致错误的代码!也就是说,如果您需要的是表现而且您并不真正关心如何到达那里。我建议你先找一下好的图书馆。例如,线性代数的Eigen将完美地满足您在此示例中的需要。如果您确实想学习SIMD编程,我建议您从更简单的情况开始,例如添加两个向量。最有可能的是,您会发现编译器能够生成比前几次尝试更好的矢量化二进制文件。但它们更直接,因此您将更容易地看到需要改进的地方。在尝试生成与编译器生成的代码一样好或更好的代码的过程中,您将学习编写最佳代码所需的各种内容。最终,您将能够为编译器无法优化的代码提供最佳实现。您需要记住的一件事是,您下层,编译器可以为您做的越少。您可以更好地控制生成二进制文件,但您也有责任使其最佳化。这些建议很模糊。抱歉,无法提供更多帮助。