【问题标题】:How do I use SSE(1,2,3,4) optimizations?如何使用 SSE(1,2,3,4) 优化?
【发布时间】:2010-07-15 14:00:52
【问题描述】:

我想知道简单地用 sse/sse2 编译我的 msvc 项目是否会有任何效果。例如,我做向量归一化和点积,但我用数学来做这些,而不是任何特定的函数。我应该使用 sse_dot() 和 sse_normalize() 来实际利用,还是编译器会知道?

谢谢

【问题讨论】:

  • 你为什么不简单地尝试一下?

标签: c++ c optimization compiler-construction


【解决方案1】:

据我了解,使用 sse2 编译器选项将导致编译器使用标量而非向量 sse2 指令代替普通 fpu 代码。我不认为它会做任何矢量化。 sse2 标量的东西肯定比 fpu 快。

要使用向量单元,您需要直接使用内在函数 ( xmmintrin.h ) 或使用第三方库。如果您只是在做简单的矢量/矩阵渲染,Bullet SDK 有一个 sse 优化的矢量数学库,这还不错。 IIRC DirectX/XNAmath 库也经过 sse 优化。

如果这些都不符合您的要求,Google 应该会提供许多替代方案。

【讨论】:

  • 作为语言扩展,gcc 具有内置的向量类型和伪函数,如果可用的话,它们可以通过 sse 指令实现。就 gcc 在架构上运行而言,这应该是可移植的,并且还应该适用于派生编译器,如 icc。
【解决方案2】:

或者您可以通过使用 Eigen、BLAS、Intel MKL 等高性能库来避免显式编写 SSE 内容……除非您正在研究嵌入式系统,否则这些库将比您想出的任何东西都要好得多与。

【讨论】:

    【解决方案3】:

    要么自己编写 SSE 代码(asm 或内在函数),要么使用第三方 SSE 优化库(例如 IPP、MKL 等),或者使用自动矢量化编译器,例如英特尔的 ICC。

    【讨论】:

      【解决方案4】:

      并不是所有的编译器都像你想象的那么聪明。甚至 gcc 也可能并不总是优化最明显的代码。请参阅以下示例并自行尝试。 icc 似乎能够优化内部循环,但是 gcc,我尝试了几个设置,不能。必要时,您必须使用 SSE 函数手动调用 SSE/SSE2 指令。人们告诉我this 是一个很好的教程。

      编辑:以下示例适用于 Mac/Linux gcc。但它在linux上失败了icc。我不知道为什么。顺便说一句,在矢量化方面,icc 被认为比 gcc 更好。

      #include <stdlib.h>
      #include <stdio.h>
      #include <time.h>
      #include <math.h>
      #include <emmintrin.h>
      
      float **mm_init(int n)
      {
          float **m;
          int i;
          m = (float**)malloc(n * sizeof(void*));
          for (i = 0; i < n; ++i)
              m[i] = calloc(n, sizeof(float));
          return m;
      }
      void mm_destroy(int n, float **m)
      {
          int i;
          for (i = 0; i < n; ++i) free(m[i]);
          free(m);
      }
      float **mm_gen(int n)
      {
          float **m;
          int i, j;
          m = mm_init(n);
          for (i = 0; i < n; ++i)
              for (j = 0; j < n; ++j)
                  m[i][j] = 2 * drand48() - 1.0;
          return m;
      }
      // better cache performance by transposing the second matrix
      float **mm_mul2(int n, float *const *a, float *const *b)
      {
          int i, j, k;
          float **m, **c;
          m = mm_init(n); c = mm_init(n);
          for (i = 0; i < n; ++i) // transpose
              for (j = 0; j < n; ++j)
                  c[i][j] = b[j][i];
          for (i = 0; i < n; ++i) {
              float *p = a[i], *q = m[i];
              for (j = 0; j < n; ++j) {
                  float t = 0.0, *r = c[j];
                  for (k = 0; k < n; ++k)
                      t += p[k] * r[k];
                  q[j] = t;
              }
          }
          mm_destroy(n, c);
          return m;
      }
      // explicit SSE optimization for the inner loop
      float **mm_mul3(int n, float *const *a, float *const *b)
      {
          int i, j, k;
          float **m, **c, x[4];
          m = mm_init(n); c = mm_init(n);
          for (i = 0; i < n; ++i) // transpose
              for (j = 0; j < n; ++j)
                  c[i][j] = b[j][i];
          for (i = 0; i < n; ++i) {
              float *p = a[i], *q = m[i];
              for (j = 0; j < n; ++j) {
                  __m128 t = _mm_setzero_ps();
                  float *r = c[j];
                  for (k = 0; k < n; k += 4) // four operations in one CPU cycle
                      t = _mm_add_ps(t, _mm_mul_ps(_mm_load_ps(p+k), _mm_load_ps(r+k)));
                  _mm_store_ps(x, t);
                  q[j] = x[0] + x[1] + x[2] + x[3];
              }
          }
          mm_destroy(n, c);
          return m;
      }
      
      int main(int argc, char *argv[])
      {
          int n = 100;
          float **a, **b, **m;
          clock_t t;
          if (argc > 1) n = atoi(argv[1]);
          n = (n + 3) / 4 * 4; // for simplicity, n can be divided by 4
          srand48(11);
          a = mm_gen(n); b = mm_gen(n);
      
          t = clock();
          m = mm_mul2(n, a, b);
          fprintf(stderr, "cache:  %lf sec; M[%d][%d]=%f\n", (double)(clock() - t) / CLOCKS_PER_SEC, n/2, n/2, m[n/2][n/2]);
      
          t = clock();
          m = mm_mul3(n, a, b);
          fprintf(stderr, "SSE:    %lf sec; M[%d][%d]=%f\n", (double)(clock() - t) / CLOCKS_PER_SEC, n/2, n/2, m[n/2][n/2]);
      
          mm_destroy(n, a); mm_destroy(n, b); mm_destroy(n, m);
          return 0;
      }
      

      【讨论】:

        【解决方案5】:

        如果您启用 SSE2,那么编译器将在您背后使用它。你永远不会注意到也不需要知道,除非你打算支持没有 SSE2 的 CPU。这与任何其他底层 CPU 指令相同。

        【讨论】:

        • 仅在 x87 上使用 SSE2 代码路径远不能自动矢量化代码。自动矢量化是一个非常棘手的话题,编译器在这方面很差(当它完全支持时)
        猜你喜欢
        • 2014-01-13
        • 2011-12-09
        • 2018-01-12
        • 2011-12-16
        • 1970-01-01
        • 1970-01-01
        • 2016-09-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多