我一直试图找出应用程序中的性能问题,并最终将其缩小到一个非常奇怪的问题.如果VZEROUPPER指令被注释掉,则下面的代码在Skylake CPU(i5-6500)上运行速度慢6倍.我测试了Sandy Bridge和Ivy Bridge CPU,两种版本都以相同的速度运行,有或没有VZEROUPPER.
现在我VZEROUPPER对这个代码有了一个相当好的想法,而且我认为当没有VEX编码指令并且没有调用可能包含它们的任何函数时,它对这个代码根本不重要.事实上它不支持其他支持AVX的CPU似乎支持这一点.英特尔®64和IA-32架构优化参考手册中的表11-2也是如此
那么发生了什么?
我留下的唯一理论是,CPU中存在一个错误,它错误地触发了"保存AVX寄存器的上半部分"程序,而不应该这样做.或者其他一些同样奇怪的东西.
这是main.cpp:
#include <immintrin.h>
int slow_function( double i_a, double i_b, double i_c );
int main()
{
/* DAZ and FTZ, does not change anything here. */
_mm_setcsr( _mm_getcsr() | 0x8040 );
/* This instruction fixes performance. */
__asm__ __volatile__ ( "vzeroupper" : : : );
int r = 0;
for( unsigned j = 0; j < 100000000; ++j )
{
r |= slow_function(
0.84445079384884236262,
-6.1000481519580951328, …Run Code Online (Sandbox Code Playgroud) 似乎是一个反复出现的问题,许多英特尔处理器(直到Skylake,除非我错了)在将AVX-256指令与SSE指令混合时表现不佳.
根据英特尔的文档,这是由SSE指令定义为保留YMM寄存器的高128位引起的,因此为了能够通过不使用AVX数据路径的高128位来节省功耗,CPU会存储这些位当执行SSE代码并在输入AVX代码时重新加载它们时,存储和加载是昂贵的.
但是,我找不到明显的理由或解释为什么SSE指令需要保留那些高128位.相应的128位VEX指令(使用它避免了性能损失)通过始终清除YMM寄存器的高128位而不是保留它们来工作.在我看来,当英特尔定义AVX架构,包括将XMM寄存器扩展到YMM寄存器时,他们可以简单地定义SSE指令也将清除高128位.显然,由于YMM寄存器是新的,可能没有遗留代码依赖于保留这些位的SSE指令,而且在我看来,英特尔可以很容易地看到这一点.
那么,英特尔定义SSE指令以保留YMM寄存器的高128位的原因是什么?它有用吗?
AMD CPU通过解码为两个128b操作来处理256b AVX指令.例如,vaddps ymm0, ymm1,ymm1在AMD上,Steamroller解码为2个宏操作,吞吐量的一半vaddps xmm0, xmm1,xmm1.
XOR归零是一种特殊情况(没有输入依赖性,并且在Jaguar上至少避免消耗物理寄存器文件条目,并且使得来自该寄存器的movdqa在发出/重命名时被消除,就像Bulldozer一直在做非零的REG)中. 但它是否足够vxorps ymm0,ymm0,ymm0早被检测到仍然只能解码为1个具有相同性能的宏操作 vxorps xmm0,xmm0,xmm0?(不像vxorps ymm3, ymm2,ymm1)
或者,在已经解码为两个uop之后,独立检测是否会发生?此外,AMD CPU上的向量xor-zeroing是否仍然使用执行端口?在Intel-CPU上,Nehalem需要一个端口,但Sandybridge系列在发布/重命名阶段处理它.
Agner Fog的指令表没有列出这个特例,他的微指南没有提到uop的数量.
这可能意味着vxorps xmm0,xmm0,xmm0更好的实施方式_mm256_setzero_ps().
对于AVX512 _mm512_setzero_ps(),如果可能的话,也只使用VEX编码的归零惯用语而不是EVEX来保存字节.(即对于zmm0-15. vxorps xmm31,xmm31,xmm31仍然需要EVEX).gcc/clang目前使用他们想要的任何寄存器宽度的xor-zeroing习语,而不是总是使用AVX-128.
报告为clang bug 32862和gcc bug 80636.MSVC已经使用了xmm.尚未向ICC报告,ICC也使用zmm regs进行AVX512归零.(虽然英特尔可能不会改变,因为目前任何英特尔CPU都没有任何好处,只有AMD.如果他们发布的低功耗CPU将矢量分成两半,他们可能.他们目前的低功耗设计(Silvermont)没有t支持AVX,只支持SSE4.)
我知道使用AVX-128指令清零256b寄存器唯一可能的缺点是它不会触发Intel CPU上256b执行单元的预热.可能会破坏试图加热它们的C或C++黑客攻击.
(在第一个256b指令之后的第一个~56k周期内,256b向量指令较慢.请参阅Agner Fog微格式pdf中的Skylake部分).如果调用noinline返回的函数_mm256_setzero_ps不是预热执行单元的可靠方法,那可能没问题.(一个仍然可以在没有AVX2的情况下工作,并且避免任何负载(可以缓存未命中)是__m128 onebits = _mm_castsi128_ps(_mm_set1_epi8(0xff));
return _mm256_insertf128_ps(_mm256_castps128_ps256(onebits), onebits)应该编译为pcmpeqd xmm0,xmm0,xmm0/ vinsertf128 ymm0,xmm0,1.对于你曾经呼叫一次预热(或保持)执行单元的事情,这仍然是非常微不足道的.关键循环.如果你想要内联的东西,你可能需要inline-asm.)
我没有AMD硬件所以我无法测试这个.
如果有人拥有AMD硬件但不知道如何测试,请使用perf计数器来计算周期(最好是m-ops或uops或AMD称之为的任何内容).
这是我用来测试短序列的NASM/YASM源:
section .text
global _start …Run Code Online (Sandbox Code Playgroud) 随着AVX的推出,英特尔将VEX编码方案引入了英特尔64和IA-32架构.该编码方案主要用于AVX指令.我想知道是否可以混合使用VEX编码指令和现在称为"传统SSE"的指令.
我问这个问题的主要原因是代码大小.请考虑以下两条说明:
shufps xmm0, xmm0, 0
vshufps xmm0, xmm0, xmm0, 0
Run Code Online (Sandbox Code Playgroud)
我通常使用第一个"广播"标量值到XMM寄存器中的所有位置.现在,指令集表明这两者之间的唯一区别(在这种情况下)是VEX编码的一个清除了YMM寄存器的高位(> = 128).假设我不需要,在这种情况下使用VEX编码版本有什么好处?第一条指令需要4个字节(0FC6C000),第二条指令需要5个(C5F8C6C000).
提前感谢所有答案.
到目前为止,我已经设法找到:
有关在Windows上使用SSE3,SSSE3,SSE4.1,SSE 4.2,AVX2和AVX-512的注意事项吗?
一些澄清:如果我使用其中一个SSE/AVX集合的指令,我需要这个来确定我的程序运行的操作系统.
我理解VZEROUPPER在混合SSE和AVX代码时使用很重要,但如果我只使用AVX(和普通的x86-64代码)而不使用任何传统的SSE指令呢?
如果我从不在我的代码中使用单个SSE指令,是否有任何性能原因我需要使用VZEROUPPER?
这假设我没有调用任何外部库(可能使用SSE).
说,我想清除4个zmm寄存器.
以下代码是否会提供最快的速度?
vpxorq zmm0, zmm0, zmm0
vpxorq zmm1, zmm1, zmm1
vpxorq zmm2, zmm2, zmm2
vpxorq zmm3, zmm3, zmm3
Run Code Online (Sandbox Code Playgroud)
在AVX2上,如果我想清除ymm寄存器,vpxor比vxorps更快,速度更快,因为vpxor可以在多个单元上运行.
在AVX512上,我们没有用于zmm寄存器的vpxor,只有vpxorq和vpxord.这是清除寄存器的有效方法吗?当我使用vpxorq清除zmm寄存器时,CPU是否足够智能,不会对zmm寄存器的先前值产生错误依赖?
在没有物理AVX512 CPU测试的情况下 - 也许有人在Knights Landing上测试过?是否有任何延迟发布?
我在内核中找不到很多SIMD指令(如SSE/AVX)(除了一个用于加速RAID6奇偶校验计算的地方).
Q1)是否有任何特定原因或仅缺少用例?
Q2)如果我想使用SIMD指令,比如设备驱动程序,今天需要做什么?
Q3)将像ISPC这样的框架纳入内核(仅用于实验)有多难?
英特尔的内在指南列出了内在的_mm256_loadu_epi32:
_m256i _mm256_loadu_epi32 (void const* mem_addr);
/*
Instruction: vmovdqu32 ymm, m256
CPUID Flags: AVX512VL + AVX512F
Description
Load 256-bits (composed of 8 packed 32-bit integers) from memory into dst.
mem_addr does not need to be aligned on any particular boundary.
Operation
a[255:0] := MEM[mem_addr+255:mem_addr]
dst[MAX:256] := 0
*/
Run Code Online (Sandbox Code Playgroud)
但是 clang 和 gcc 不提供这种内在的。相反,他们avx512vlintrin.h仅提供(在文件中)屏蔽版本
_mm256_mask_loadu_epi32 (__m256i, __mmask8, void const *);
_mm256_maskz_loadu_epi32 (__mmask8, void const *);
Run Code Online (Sandbox Code Playgroud)
归结为相同的指令vmovdqu32。我的问题:我怎样才能模仿_mm256_loadu_epi32:
inline _m256i _mm256_loadu_epi32(void const* mem_addr)
{
/* …Run Code Online (Sandbox Code Playgroud) avx ×7
assembly ×5
x86 ×5
avx512 ×3
performance ×3
sse ×3
c ×1
c++ ×1
intel ×1
intrinsics ×1
ispc ×1
linux-kernel ×1
simd ×1
windows ×1
xeon-phi ×1