14

这是我第一次使用 SSE 内在函数。我正在尝试使用英特尔 SSE 内在函数(最高 SSE4.2)将一段简单的代码转换为更快的版本。我似乎遇到了一些错误。

代码的标量版本是:(简单的矩阵乘法)

     void mm(int n, double *A, double *B, double *C)
     {
        int i,j,k;
        double tmp;

        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++) {
                    tmp = 0.0;
                    for(k = 0; k < n; k++)
                            tmp += A[n*i+k] *
                                   B[n*k+j];
                    C[n*i+j] = tmp;

              }
            }

这是我的版本:我已包含#include <ia32intrin.h>

      void mm_sse(int n, double *A, double *B, double *C)
      {
        int i,j,k;
        double tmp;
        __m128d a_i, b_i, c_i;

        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++) {
                    tmp = 0.0;
                    for(k = 0; k < n; k+=4)
                            a_i = __mm_load_ps(&A[n*i+k]);
                            b_i = __mm_load_ps(&B[n*k+j]);
                            c_i = __mm_load_ps(&C[n*i+j]);

                            __m128d tmp1 = __mm_mul_ps(a_i,b_i);
                            __m128d tmp2 = __mm_hadd_ps(tmp1,tmp1);
                            __m128d tmp3 = __mm_add_ps(tmp2,tmp3);
                            __mm_store_ps(&C[n*i+j], tmp3);

            }
         }

我哪里错了?我收到几个这样的错误:

mm_vec.c(84):错误:“int”类型的值不能分配给“__m128d”类型的实体 a_i = __mm_load_ps(&A[n*i+k]);

这就是我的编译方式: icc -O2 mm_vec.c -o vec

有人可以帮我准确转换此代码。谢谢!

更新:

根据您的建议,我做了以下更改:

       void mm_sse(int n, float *A, float *B, float *C)
       {
         int i,j,k;
         float tmp;
         __m128 a_i, b_i, c_i;

         for(i = 0; i < n; i++)
            for(j = 0; j < n; j++) {
                    tmp = 0.0;
                    for(k = 0; k < n; k+=4)
                            a_i = _mm_load_ps(&A[n*i+k]);
                            b_i = _mm_load_ps(&B[n*k+j]);
                            c_i = _mm_load_ps(&C[n*i+j]);

                            __m128 tmp1 = _mm_mul_ps(a_i,b_i);
                            __m128 tmp2 = _mm_hadd_ps(tmp1,tmp1);
                            __m128 tmp3 = _mm_add_ps(tmp2,tmp3);
                            _mm_store_ps(&C[n*i+j], tmp3);


            }
        }

但现在我似乎遇到了分段错误。我知道这可能是因为我没有正确访问数组 A、B、C 的数组下标。我对此很陌生,不知道如何进行。

请帮助我确定处理此代码的正确方法。

4

2 回答 2

10

您看到的错误是因为函数名称中有太多下划线,例如:

__mm_mul_ps

应该:

_mm_mul_ps // Just one underscore up front

所以 C 编译器假设它们返回int,因为它没有看到声明。

除此之外,尽管还有其他问题-您似乎正在混合对同一指令的双浮点变体和单浮点变体的调用。

例如,您有:

__m128d a_i, b_i, c_i;

但你打电话:

__mm_load_ps(&A[n*i+k]);

它返回一个__m128not a __m128d- 你想调用:

_mm_load_pd

反而。如果您希望他们处理成对的双打,其他说明也是如此。


如果您在 SSE 代码中看到无法解释的分段错误,我倾向于猜测您遇到了内存对齐问题 - 传递给 SSE 内在函数的指针(主要是1)需要 16 字节对齐。您可以在代码中使用简单的断言来检查这一点,或者在调试器中检查它(如果指针正确对齐,您希望指针的最后一位为 0)。

如果它没有正确对齐,您需要确保它是正确的。对于没有分配的东西new/malloc()您可以使用编译器扩展来执行此操作(例如使用 gcc):

float a[16] __attribute__ ((aligned (16)));

前提是您的 gcc 版本具有足够大的最大对齐以支持这一点以及有关堆栈对齐的其他一些注意事项。对于动态分配的存储,您需要使用特定于平台的扩展,例如posix_memalign分配合适的存储:

float *a=NULL;
posix_memalign(&a, __alignof__(__m128), sizeof(float)*16);

(我认为使用 C++11 可能会有更好、更便携的方法,但我还不能 100% 确定这一点)。

1有一些指令允许您执行未对齐的加载和存储,但与对齐的加载相比,它们非常慢,如果可能的话,值得避免。

于 2012-06-08T17:07:25.450 回答
3

您需要确保您的加载和存储始终访问 16 字节对齐的地址。或者,如果由于某种原因您不能保证这一点,那么使用_mm_loadu_ps/_mm_storeu_ps代替_mm_load_ps/ _mm_store_ps- 这会降低效率,但不会在未对齐的地址上崩溃。

于 2012-06-08T19:31:55.347 回答