考虑以下代码,其中a是float的参数数组,s是float的初始未初始化结果数组:
s[n - 1] = mu * a[n - 1];
for (int j = n - 2; j >= 0; j--)
s[j] = mu * (a[j] + s[j + 1]);
return s;
是否有机会使用SIMD(AVX2)提高这种循环代码的性能?
编辑:后来我发现这个公式/算法被称为“贴现总和”,但在网上找不到它的并行版本。
如果你是指矢量化,那么不,不是直接的。由于计算使用上一次迭代的值来计算下一次迭代,因此它并不是简单的矢量化。
此外,未对齐使用可能会导致问题。也可能从末端循环。
如果您指的是SIMD集中的指令,那么可能可以使用它们,但不一定带来巨大的好处,而且编译器通常都知道如何做到这一点。
将表达式编写为矩阵向量积有时会有所帮助。假设您已经知道ₖ₊₈ 您可以计算ₖ 至<代码>sₖ₊₇ 自<代码>aₖ 至<代码>aₖ₊₇ 使用
[ µ µ² µ³ µ⁴ µ⁵ µ⁶ µ⁷ µ⁸] [aₖ₊₀ ]
[ 0 µ µ² µ³ µ⁴ µ⁵ µ⁶ µ⁷] [aₖ₊₁ ]
[ 0 0 µ µ² µ³ µ⁴ µ⁵ µ⁶] [aₖ₊₂ ]
[ 0 0 0 µ µ² µ³ µ⁴ µ⁵] [aₖ₊₃ ]
[ 0 0 0 0 µ µ² µ³ µ⁴] * [aₖ₊₄ ]
[ 0 0 0 0 0 µ µ² µ³] [aₖ₊₅ ]
[ 0 0 0 0 0 0 µ µ²] [aₖ₊₆ ]
[ 0 0 0 0 0 0 0 µ ] [aₖ₊₇+sₖ₊₈]
由于s
在计算时可能会有一些延迟,因此将其移出产品是有意义的。这可以通过一次广播和一次融合多添加来计算:
[ µ µ² µ³ µ⁴ µ⁵ µ⁶ µ⁷ µ⁸] [aₖ₊₀] [ µ⁸]
[ 0 µ µ² µ³ µ⁴ µ⁵ µ⁶ µ⁷] [aₖ₊₁] [ µ⁷]
[ 0 0 µ µ² µ³ µ⁴ µ⁵ µ⁶] [aₖ₊₂] [ µ⁶]
[ 0 0 0 µ µ² µ³ µ⁴ µ⁵] [aₖ₊₃] [ µ⁵]
[ 0 0 0 0 µ µ² µ³ µ⁴] * [aₖ₊₄] + [ µ⁴] * sₖ₊₈
[ 0 0 0 0 0 µ µ² µ³] [aₖ₊₅] [ µ³]
[ 0 0 0 0 0 0 µ µ²] [aₖ₊₆] [ µ²]
[ 0 0 0 0 0 0 0 µ ] [aₖ₊₇] [ µ ]
第一个矩阵可以分解为三个矩阵,每个矩阵可以使用一个随机和一个FMA计算:
[ 1 0 0 0 µ⁴ 0 0 0 ] [ 1 0 µ² 0 0 0 0 0 ] [ µ µ² 0 0 0 0 0 0 ] [aₖ₊₀] [ µ⁸]
[ 0 1 0 0 µ³ 0 0 0 ] [ 0 1 µ 0 0 0 0 0 ] [ 0 µ 0 0 0 0 0 0 ] [aₖ₊₁] [ µ⁷]
[ 0 0 1 0 µ² 0 0 0 ] [ 0 0 1 0 0 0 0 0 ] [ 0 0 µ µ² 0 0 0 0 ] [aₖ₊₂] [ µ⁶]
[ 0 0 0 1 µ 0 0 0 ] [ 0 0 0 1 0 0 0 0 ] [ 0 0 0 µ 0 0 0 0 ] [aₖ₊₃] [ µ⁵]
[ 0 0 0 0 1 0 0 0 ] * [ 0 0 0 0 1 0 µ² 0 ] * [ 0 0 0 0 µ µ² 0 0 ] * [aₖ₊₄] + [ µ⁴] * sₖ₊₈
[ 0 0 0 0 0 1 0 0 ] [ 0 0 0 0 0 1 µ 0 ] [ 0 0 0 0 0 µ 0 0 ] [aₖ₊₅] [ µ³]
[ 0 0 0 0 0 0 1 0 ] [ 0 0 0 0 0 0 1 0 ] [ 0 0 0 0 0 0 µ µ²] [aₖ₊₆] [ µ²]
[ 0 0 0 0 0 0 0 1 ] [ 0 0 0 0 0 0 0 1 ] [ 0 0 0 0 0 0 0 µ ] [aₖ₊₇] [ µ ]
最右边的矩阵向量积实际上是多乘一次。
总的来说,对于8个元素,您需要4个FMA,一个乘法和4个洗牌/广播($$$$
意味着这里可以有任何(有限的)元素——或者,如果这些元素保证为0,则可以部分共享向量。所有向量首先表示为最低有效,所有乘法均为元素):
bₖₖ₊₇ = [aₖ₊₀, aₖ₊₁, aₖ₊₂, aₖ₊₃, aₖ₊₄, aₖ₊₅, aₖ₊₆, aₖ₊₇] * [µ µ µ µ µ µ µ µ ] vmulps
bₖₖ₊₇ += [aₖ₊₁, $$$$, aₖ₊₃, $$$$, aₖ₊₅, $$$$, aₖ₊₆, $$$$] * [µ² 0 µ² 0 µ² 0 µ² 0 ] vshufps (or vpsrlq) + vfmadd
cₖₖ₊₇ = bₖₖ₊₇
cₖₖ₊₇ += [bₖ₊₂, bₖ₊₂, $$$$, $$$$, bₖ₊₆, bₖ₊₆, $$$$, $$$$] * [µ² µ 0 0 µ² µ 0 0 ] vshufps + vfmadd
dₖₖ₊₇ = cₖₖ₊₇
dₖₖ₊₇ += [cₖ₊₄, cₖ₊₄, cₖ₊₄, cₖ₊₄, $$$$, $$$$, $$$$, $$$$] * [µ⁴ µ³ µ² µ 0 0 0 0 ] vpermps + vfmadd
sₖₖ₊₇ = dₖₖ₊₇
+ [sₖ₊₈, sₖ₊₈, sₖ₊₈, sₖ₊₈, sₖ₊₈, sₖ₊₈, sₖ₊₈, sₖ₊₈] * [µ⁸ µ⁷ µ⁶ µ⁵ µ⁴ µ³ µ² µ ] vbroadcastss + vfmadd
如果我分析正确,倍数的计算ₖ
可以交错,这将抵消延迟。唯一的热路径是最终的vbroadcastss vfmadd,用于计算ₖₖ₊₇
自<代码>dₖₖ₊₇
和<代码>sₖ₊₈
。计算16个s的块可能是值得的ₖ
和fmaddⁱ * sₖ₊₁₆
到该值。此外,用于计算<代码>d的FMAₖ
仅使用一半的元素。通过一些复杂的旋转,可以用相同数量的FMA计算两个块(我认为这不值得努力,但可以自由尝试)。
比较而言:纯标量实现需要对8个元素进行8次加法和8次乘法,并且每个操作都取决于之前的结果。
N、 B.如果您计算的不是公式,而是:
sₖ = aₖ₊₁ + µ*sₖ₊₁
此外,在标量版本中,您将拥有融合多加法,而不是先加法再乘法。结果只会相差
µ
的一个因子。
相关:是否可以在计算中对串行依赖项使用SIMD,如指数移动平均滤波器?-如果前面有一个n步的封闭式公式,您可以使用它来回避序列依赖项。但我认为情况并非如此。
这看起来像是一种串行依赖的前缀和类型,位于垂直加法的顶部,带有a[j]
。有一些方法可以加速,例如获得O(SIMD\u width/log(SIMD\u width))的加速。
问题内容: 在大多数情况下,Java 8流允许比老式循环可读得多的代码。但是,根据我自己的经验和所阅读的内容,使用流而不是for循环可能会导致性能下降(或有时会有所改善),这有时很难预测。 在大型项目中,为每个循环编写基准测试似乎不可行,因此, 在决定是否用流替换循环时,关键因素是什么(例如,预期的集合大小,预期的百分比值被删除)。过滤,迭代操作的复杂性,归约或聚合的类型等) ,这些指标可能暗示可
我正在实现两相矩阵乘法。下面是第一阶段的减速器。键是左文件的行索引和右文件的列索引。我希望映射和减速器的输出计数相同。但是看起来内环递增的迭代器与外环的迭代器相同,因此减速器输出的数量等于键的数量。 代码片: 但是当我有如下简单的java应用程序时: 这里的输出数是4。如果内环的工作原理与减速机相同,则输出计数应为1,即(10-15)。 有人能解释这种行为吗。 维沙尔
在C/C中,可以对SIMD(如AVX和AVX2)指令使用内部函数。有没有办法在Rust中使用SIMD?
在循环中,我们不断终止条件,并在每一次传递中检查这些条件。 我见过2种检查方法 1.或
我有int的向量,我需要找到并用特定的值替换一些元素。他们都是一样的 例如:将所有元素的4替换为8。 我正在尝试c中循环中的直接内存访问。但对我来说还是很慢。 更新: 我正在上使用OpenCV对象: 函数仅在释放模式下通过指针返回值
hasNext()的定义是“如果此扫描仪的输入中有另一个标记,则返回true。此方法可能会在等待输入扫描时阻塞。扫描仪不会前进超过任何输入。” 当我把 standardInput.hasNext() 放在 for 循环中时,程序会向无穷大运行。但是如果我把它放在 while-loop 中,它不会运行到无穷大。这两个程序之间的区别在哪里,为什么其中一个有效而另一个无效? for循环: while-l