[英]Speed up float 5x5 matrix * vector multiplication with SSE
我需要每秒运行 240000 次矩阵向量乘法。 矩阵是 5x5 并且始终相同,而向量在每次迭代时都会发生变化。 数据类型是float
。 我正在考虑使用一些 SSE(或类似)指令。
我担心算术运算的数量与涉及的 memory 运算的数量相比太少了。 你认为我能得到一些切实的(例如> 20%)改进吗?
我需要英特尔编译器吗?
你能指出一些参考资料吗?
原则上,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的行,但在 memory 中, U存储为xyzwtxyzwtxyzwtxyzwt ,因此我们必须将其转置为xxxxyyyyzzzzwwwwtttt 。 这可以通过 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 倍。
我建议使用英特尔 IPP 并抽象出对技术的依赖
如果您使用的是 GCC,请注意 -O3 选项将启用自动矢量化,这将在许多情况下自动生成 SSE 或 AVX 指令。 一般来说,如果你只是把它写成一个简单的for循环,GCC 会将它向量化。 有关详细信息,请参阅http://gcc.gnu.org/projects/tree-ssa/vectorization.html 。
这应该很容易,尤其是当您使用 Core 2 或更高版本时:您需要 5* _mm_dp_ps
、一个_mm_mul_ps
、两个_mm_add_ps
、一个普通乘法,以及一些随机播放、加载和存储(如果矩阵是固定的,您可以保留大部分都在 SSE 寄存器中,如果您不需要它们用于其他任何事情)。
至于 memory 带宽:我们说的是 2.4 兆字节的向量,而 memory 的带宽是个位数千兆字节每秒。
对向量有什么了解? 由于矩阵是固定的,并且如果向量可以采用的值数量有限,那么我建议您预先计算计算并使用表格查找来访问它们。
以循环换取 memory 的经典优化技术...
我建议查看优化的 BLAS 库,例如 Intel MKL 或 AMD ACML。 根据您的描述,我假设您将在SGEMV
2 级矩阵向量例程之后执行y = A*x
样式操作。
如果你真的想自己实现一些东西,使用(可用的) SSE..SSE4
和AVX
指令集在某些情况下可以提供显着的性能改进,尽管这正是一个好的 BLAS 库要做的事情。 您还需要仔细考虑缓存友好的数据访问模式。
我不知道这是否适用于您的情况,但是您可以一次对向量的“块”进行操作吗? 因此,您可以对[y1 y2... yn] = A * [x1 x2... xn]
的块进行操作,而不是重复执行y = A*x
样式的操作。 如果是这样,这意味着您可以使用优化的矩阵矩阵例程,例如SGEMM
。 由于数据访问模式,这可能比重复调用SGEMV
更有效。 如果是我,我会尝试沿着这条路走 go ......
希望这可以帮助。
如果您事先知道向量(例如,一次完成所有 240k),那么通过并行化循环比通过 SSE 获得更好的加速。 如果您已经迈出了这一步,或者您不是一下子了解所有这些,那么 SSE 可能会带来很大的好处。
如果 memory 是连续的,那么不要太担心 memory 操作。 如果你有一个链表或其他东西,那么你就有麻烦了,但它应该能够跟上而没有太多问题。
5x5 是一个有趣的大小,但您可以在一条 SSE 指令中至少执行 4 次翻转,并尝试减少算术开销。 您不需要英特尔编译器,但它可能会更好,我听说过关于它如何使用算术代码更好的传说。 Visual Studio 具有处理 SSE2 的内在函数,我认为最多 SSE4 取决于您的需要。 当然,你必须自己滚动它。 抓住图书馆可能是这里的明智之举。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.