|
1
3
有点;至少需要一次洗牌。更重要的是,在这种情况下,计算每个标量操作数是昂贵的,而且正如@spectras'的答案所示,gcc至少无法将其自动矢量化为
你只是用一个线性函数填充一个数组 . 你在循环中每次都在增加。这避免了循环对整数循环计数器以外的任何东西的依赖性,但是在循环中做这么多工作会遇到吞吐量瓶颈。
也就是说,你在计算
这将引入一些舍入误差,这些误差累积起来,而不是从头开始重新计算,但是
它还以引入对fp add而不是整数的串行依赖为代价。但是您已经在“优化”版本中有一个带循环的fp-add依赖链,它使用
因此,您需要使用多个向量展开以隐藏fp延迟,并同时保持至少3或4个fp添加。 . (Haswell:3周期延迟,每时钟一次吞吐量。Skylake:4周期延迟,每时钟吞吐量2。)另请参见 Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? 有关使用多个累加器展开循环执行依赖项(点积)的类似问题的详细信息。
建立时是加还是乘的选择
无论如何,这将编译成一个非常有效的gcc和msvc内部循环( on the Godbolt compiler explorer )
它有4个独立的依赖链,通过xmm0、1、2和5。所以有足够的指令级并行度来保持4
不过,如果存储吞吐量为每个时钟1个矢量,则大于1
矢量更宽的AVX(4
更好的方法是:根本不要将这些数据存储在内存中;动态地重新生成。在需要时动态生成它,如果使用它的代码只读取它几次,而且也是手动矢量化的。 |
|
|
2
3
在我的系统上,
这将输出正常版本(循环体提取):
对于intrinsics版本:
简而言之:编译器从C版本自动生成AVX代码。 在使用标志播放更多内容后进行编辑,以便仅在两种情况下使用SSE2:
编译器仍然执行与使用内部函数生成的不同的操作。编译器版本:
内部版本:
编译器不在此展开循环。这可能是更好或更糟取决于许多事情。你可能想把这两个版本都试一下。 |