【问题标题】:Speed up float 5x5 matrix * vector multiplication with SSE使用 SSE 加速浮点 5x5 矩阵 * 向量乘法
【发布时间】:2011-09-30 20:30:17
【问题描述】:

我需要每秒运行 240000 次矩阵向量乘法。矩阵是 5x5 并且始终相同,而向量在每次迭代时都会发生变化。数据类型为float。我正在考虑使用一些 SSE(或类似)指令。

  1. 我担心算术运算的数量与所涉及的内存运算的数量相比太少了。您认为我可以得到一些切实的(例如 > 20%)改进吗?

  2. 我需要英特尔编译器吗?

  3. 您能指出一些参考资料吗?

【问题讨论】:

  • 发表评论而不是答案,因为这只是推测,但某些编译器是否优化了各种矩阵乘法运算?我似乎记得一个古老的大学项目,即嵌套 for 循环乘法与线程乘法相比,由于优化,它的运行时间大大加快......
  • 如果您写过任何代码,请发帖。多少次是“可怕的”?今天需要多长时间,您想达到什么目标?
  • 这也需要在几乎任何 x86 CPU 上工作,或者我们可以假设例如英特尔和 SSSE3 或更高版本?
  • @Alexandre C.:矩阵?复数?问题说“总是一样”。此外,5*5*sizeof(double) 远远小于 L1 缓存的大小。为什么会出现缓存未命中?

标签: c++ vectorization matrix-multiplication sse simd


【解决方案1】:

用于向量、矩阵等的Eigen C++ 模板库...

  • 针对固定大小的小矩阵(以及动态大小的矩阵)的优化代码

  • 使用 SSE 优化的优化代码

所以你应该试一试。

【讨论】:

  • 请注意,Eigen 文档声称它不适用于大小不是 16 字节的倍数的固定向量,因此它可能不会针对此问题自动向量化。我不能说 Eigen3 是否仍然如此。
  • 感谢您的注意——我不知道这个限制。但是我还是更多地使用动态大小的向量和矩阵。
  • @John L 感谢您的评论。是的,我在文档中发现了相同的内容。您认为这是因为 SSE 优化或库的潜在限制吗?谢谢!
  • @Enzo:关于 SSE。 SSE 在一条指令中执行 X,通常是 4,触发器。如果你不是 4 的倍数(*4byte floats = 16bytes),那么你不能只用 SSE 指令来表达操作。
  • @Enzo - DeadMG 完全正确。如果 Eigen 不起作用,请尝试自己动手。 SSE 内部函数的 MSDN 文档非常好,其他编译器基本相同。
【解决方案2】:

我建议使用英特尔 IPP 并抽象出对技术的依赖

【讨论】:

  • 他们可能知道很多关于利用英特尔处理器缓存的技巧。您应该与 Eigen 进行比较,但恕我直言 IPP 是一个更好的产品。
【解决方案3】:

对向量有什么了解?由于矩阵是固定的,并且如果向量可以采用的值数量有限,那么我建议您预先计算计算并使用表格查找来访问它们。

用内存换循环的经典优化技术...

【讨论】:

  • 对我来说似乎很乐观,向量可以取的值应该是合理有限的,但相应地量化向量可能不是问题。为了更好地做到这一点,可以在这些量化向量之间进行插值并获得更好的结果——但这可能比经过适当优化的直接矩阵乘法要慢。
  • @leftaroundabout - 也许,也许不是。由 OP 收集有关输入的统计信息,然后决定是否可以使用它。在之前的一个项目中,我发现超过 95% 的对高度复杂的计算函数的调用具有非常有限的范围,预先计算了这些,使处理速度提高了一个数量级或更多。如果在查表中没有找到,那么我们将求助于从头开始计算。
  • 感谢您的回复!不幸的是我不能那样做。它是一个实时软件,可能的向量数量是无限的。
【解决方案4】:

我建议您查看优化的 BLAS 库,例如 Intel MKL 或 AMD ACML。根据您的描述,我假设您会在 SGEMV 2 级矩阵向量例程之后执行 y = A*x 样式操作。

如果您真的想自己实现某些东西,使用(可用的)SSE..SSE4AVX 指令集在某些情况下可以显着提高性能,尽管这正是一个好的 BLAS 库要做的事情。您还需要仔细考虑缓存友好的数据访问模式。

我不知道这是否适用于您的情况,但是您可以一次对“块”向量进行操作吗?因此,您可以对[y1 y2 ... yn] = A * [x1 x2 ... xn] 的块进行操作,而不是重复执行y = A*x 样式的操作。如果是这样,这意味着您可以使用优化的矩阵矩阵例程,例如SGEMM。由于数据访问模式,这可能比重复调用SGEMV 更有效。如果是我,我会尝试走这条路……

希望这会有所帮助。

【讨论】:

  • 我希望一个固定的 5x5 矩阵可以完全保存在寄存器中,因此缓存访问不会有很大的影响(假设向量具有合理的布局)。因此,对于 SSE 编程的介绍来说,这似乎是一个很好的问题。尽管在尝试了编译器选项和库之后,这仍然是我最后的手段。
  • @John L:嗯??您仍然需要在使用它们之前加载寄存器,并且您肯定想要订购指令以便连续执行此操作(甚至可能还有适当的数据预取)。这就是我对“缓存友好访问模式”的理解...... :)
  • 矩阵不会改变,因此您只需在迭代开始前加载一次。 OP 的问题可能类似于y[0] = i[0]; y[n] = m*(y[n-1])。每次迭代只需要加载新向量,大多数程序员会连续执行,即使不是,编译器也更有可能发现它并重新排序。
【解决方案5】:

如果您事先知道向量(例如,一次完成所有 240k),那么通过并行化循环比通过 SSE 获得更好的加速。如果您已经迈出了这一步,或者您不是一下子了解所有这些,那么 SSE 可能会带来很大的好处。

如果内存是连续的,那么不用太担心内存操作。如果你有一个链表或其他东西,那么你就有麻烦了,但它应该能够跟上而没有太多问题。

5x5 是一个有趣的大小,但您可以在一条 SSE 指令中执行至少 4 次翻转,并尝试减少算术开销。您不需要英特尔编译器,但它可能会更好,我听说过关于它如何使用算术代码更好的传说。 Visual Studio 具有处理 SSE2 的内在函数,我认为最多 SSE4 取决于您的需要。当然,你必须自己滚动它。抓住图书馆可能是明智之举。

【讨论】:

    【解决方案6】:

    这应该很容易,尤其是当您使用 Core 2 或更高版本时:您需要 5* _mm_dp_ps、一个 _mm_mul_ps、两个 _mm_add_ps、一个普通乘法,以及一些随机播放、加载和存储(以及如果矩阵是固定的,您可以将其大部分保存在 SSE 寄存器中,如果您不需要它们用于其他任何事情)。

    至于内存带宽:我们说的是 2.4 MB 的向量,而内存带宽是每秒个位数千兆字节。

    【讨论】:

      【解决方案7】:

      如果您使用的是 GCC,请注意 -O3 选项将启用自动矢量化,这在许多情况下会自动生成 SSE 或 AVX 指令。一般来说,如果你只是把它写成一个简单的 for 循环,GCC 会将它向量化。请参阅http://gcc.gnu.org/projects/tree-ssa/vectorization.html 了解更多信息。

      【讨论】:

      • 任何体面的编译器都可以进行自动向量化,但仅限于一些简单的已知模式。对于任何其他情况,您需要自己编写矢量化代码,或者使用考虑到这一点的库
      【解决方案8】:

      原则上,SSE 的加速可以是 4 倍(AVX 是 8 倍)。让我解释一下。

      我们称您的固定 5x5 矩阵为 M。将 5D 向量的分量定义为 (x,y,z,w,t)。现在从前四个向量形成一个 5x4 矩阵 U

      U =
      xxxx
      yyyy
      zzzz
      wwww
      tttt
      

      接下来,做矩阵乘积MU = V。矩阵 V 包含 M 和前四个向量的乘积。唯一的问题是,对于 SSE,我们需要读取 U 的行,但在内存中 U 存储为 xyzwtxyzwtxyzwtxyzwt 所以我们必须转置它到 xxxyyyyzzzzwwwwtttt。这可以通过 SSE 中的洗牌/混合来完成。一旦我们有了这种格式,矩阵乘积就非常有效。

      与使用标量代码进行 O(5x5x4) 操作不同,它只需要 O(5x5) 操作,即 4 倍加速。使用 AVX 时,矩阵 U 将是 5x8,因此不需要 O(5x5x8) 运算,它只需要 O(5x5),即 8 倍加速。

      但是,矩阵 V 将采用 xxxxyyyyzzzzwwwwtttt 格式,因此根据应用程序的不同,它可能必须转换为 xyzwtxyzwtxyzwtxyzwt 格式。

      对接下来的四个向量重复此操作(AVX 为 8 个),依此类推,直到完成。

      如果您可以控制向量,例如,如果您的应用程序动态生成向量,那么您可以以 xxxxyyyyzzzzwwwwtttt 格式生成它们并避免数组的转置。在这种情况下,您应该使用 SSE 获得 4 倍的加速,使用 AVX 获得 8 倍的加速。如果将其与线程结合使用,例如OpenMP,你的加速应该接近 16 倍(假设四个物理内核)与 SSE。我认为这是 SSE 能做的最好的事情。

      编辑:由于指令级并行性 (ILP),您可以获得 2 倍的加速,因此 SSE 的加速可以在四核 (64x AVX) 的情况下提高 32 倍,并且由于 FMA3,Haswell 的加速可以再次提高 2 倍。

      【讨论】:

      • ILP 和 FMA 也将受益于标量;这不是 SIMD 独有的。那时你只是在计算理论上的最大 FLOPS/时钟,而不是相对于标量的 speedup
      猜你喜欢
      • 2011-07-05
      • 2019-04-03
      • 2016-02-22
      • 1970-01-01
      • 2017-03-11
      • 2016-10-31
      • 1970-01-01
      • 2020-02-28
      • 1970-01-01
      相关资源
      最近更新 更多