将 SSE 与 AVX128 混合使用以获得更短的指令?
Mixing SSE with AVX128 for shorter instructions?
根据我能收集到的所有信息,混合使用 SSE 和 128 位 (E)VEX 编码指令不会造成性能损失。这表明将两者混合应该没问题。当 SSE 指令通常比 VEX 等效指令短 1 个字节时,这可能是有益的。
但是,我从未见过任何人或任何编译器这样做过。例如,在 Intel 的 AVX(128 位)MD5 实现中,various vmovdqa
could be replaced with movaps
(or this vshufps
可以替换为较短的 shufps
,因为 dest 和 src1 寄存器相同。
避免 SSE 有什么特别的原因,还是我遗漏了什么?
你是对的,如果从 vzeroupper
中知道 YMM 上限为零,则混合 AVX128 和 SSE 没有任何惩罚,如果不这样做会节省代码大小,那么这是一个错过的优化。
另请注意,如果您不需要 REX 前缀,它只会节省代码大小。 2 字节 VEX 相当于 SSE1 的 REX + 0F。编译器确实尝试支持低位寄存器以希望避免 REX 前缀,但我认为他们没有考虑在每条指令中使用哪些寄存器组合来最小化总的 REX 前缀。 (或者即使他们确实尝试这样做,他们也不擅长)。人类可以花时间做这样的计划。
大多数时候它很小,只是偶尔一个字节的代码大小。这通常是一件好事,可以帮助前端。 (或者在 Intel CPU 上为 blendvps xmm, xmm, <XMM0>
而不是 pblendvps xmm, xmm, xmm, xmm
保存一个 uop(对于 pd 和 pblendvb 也是如此),如果你可以安排使用它而不需要另一个 movaps
)
如果你弄错了,不利的一面是 SSE/AVX 转换惩罚(在 Haswell 和 Ice Lake 上),或者对 Skylake 的错误依赖。 。 IDK 如果 Zen2 做了类似的事情; Zen1 将 256 位操作拆分为 2 uops 并且不关心 vzeroupper。
为了让编译器安全地执行此操作,他们必须跟踪更多内容以确保他们不会 运行 函数内部的 SSE 指令而 YMM 寄存器的上半部分变脏。编译器没有将 AVX 代码生成限制为仅 128 位指令的选项,因此他们必须开始跟踪可能弄脏 YMM 上半部分的执行路径。
但是,我认为他们无论如何都必须在整个函数的基础上这样做,以了解何时在 ret
之前使用 vzeroupper
(在不接受或 [=40= 的函数中) ] a __m256/i/d
按值,这意味着调用者已经在使用宽向量)。
但不需要 vzeroupper
与 movaps
是否性能安全是两回事,因此以类似的方式进行跟踪又是另一回事。找到可以安全避免 VEX 前缀的所有情况。
不过,在某些情况下可能很容易证明它是安全的。如果编译器使用一种保守的算法,当分支可能有也可能没有脏鞋面时有一些遗漏的优化,并且在这种情况下总是使用 VEX,并且总是使用 vzeroupper
.
根据我能收集到的所有信息,混合使用 SSE 和 128 位 (E)VEX 编码指令不会造成性能损失。这表明将两者混合应该没问题。当 SSE 指令通常比 VEX 等效指令短 1 个字节时,这可能是有益的。
但是,我从未见过任何人或任何编译器这样做过。例如,在 Intel 的 AVX(128 位)MD5 实现中,various vmovdqa
could be replaced with movaps
(or this vshufps
可以替换为较短的 shufps
,因为 dest 和 src1 寄存器相同。
避免 SSE 有什么特别的原因,还是我遗漏了什么?
你是对的,如果从 vzeroupper
中知道 YMM 上限为零,则混合 AVX128 和 SSE 没有任何惩罚,如果不这样做会节省代码大小,那么这是一个错过的优化。
另请注意,如果您不需要 REX 前缀,它只会节省代码大小。 2 字节 VEX 相当于 SSE1 的 REX + 0F。编译器确实尝试支持低位寄存器以希望避免 REX 前缀,但我认为他们没有考虑在每条指令中使用哪些寄存器组合来最小化总的 REX 前缀。 (或者即使他们确实尝试这样做,他们也不擅长)。人类可以花时间做这样的计划。
大多数时候它很小,只是偶尔一个字节的代码大小。这通常是一件好事,可以帮助前端。 (或者在 Intel CPU 上为 blendvps xmm, xmm, <XMM0>
而不是 pblendvps xmm, xmm, xmm, xmm
保存一个 uop(对于 pd 和 pblendvb 也是如此),如果你可以安排使用它而不需要另一个 movaps
)
如果你弄错了,不利的一面是 SSE/AVX 转换惩罚(在 Haswell 和 Ice Lake 上),或者对 Skylake 的错误依赖。
为了让编译器安全地执行此操作,他们必须跟踪更多内容以确保他们不会 运行 函数内部的 SSE 指令而 YMM 寄存器的上半部分变脏。编译器没有将 AVX 代码生成限制为仅 128 位指令的选项,因此他们必须开始跟踪可能弄脏 YMM 上半部分的执行路径。
但是,我认为他们无论如何都必须在整个函数的基础上这样做,以了解何时在 ret
之前使用 vzeroupper
(在不接受或 [=40= 的函数中) ] a __m256/i/d
按值,这意味着调用者已经在使用宽向量)。
但不需要 vzeroupper
与 movaps
是否性能安全是两回事,因此以类似的方式进行跟踪又是另一回事。找到可以安全避免 VEX 前缀的所有情况。
不过,在某些情况下可能很容易证明它是安全的。如果编译器使用一种保守的算法,当分支可能有也可能没有脏鞋面时有一些遗漏的优化,并且在这种情况下总是使用 VEX,并且总是使用 vzeroupper
.