为了账号安全,请及时绑定邮箱和手机立即绑定

如果没有Skylake上的VZEROUPPER,为什么这个SSE代码会慢6倍?

/ 猿问

如果没有Skylake上的VZEROUPPER,为什么这个SSE代码会慢6倍?

蛊毒传说 2019-09-02 16:09:13

我一直试图找出应用程序中的性能问题,并最终将其缩小到一个非常奇怪的问题。如果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,

                5.0302160279288017364 );

    }

    return r;

}

生成的代码与gcc不同,但它显示相同的问题。较旧版本的intel编译器生成了另一个函数的变体,它也显示了问题,但只有在main.cpp没有使用intel编译器构建时,因为它插入调用来初始化一些自己的库,这可能最终会在VZEROUPPER某处做。


当然,如果整个东西都是用AVX支持构建的,那么内在函数就会变成VEX编码指令,也没有问题。


我已经尝试perf在linux上分析代码,并且大多数运行时通常依赖于1-2条指令,但并不总是相同的,具体取决于我所分析的代码版本(gcc,clang,intel)。缩短功能似乎会使性能差异逐渐消失,因此看起来几条指令都会导致问题。


查看完整描述

3 回答

?
波斯汪

您正在经历“混合”非VEX SSE和VEX编码指令的惩罚 - 即使您的整个可见应用程序显然不使用任何AVX指令!

在Skylake之前,当从使用vex的代码切换到没有使用vex的代码时,这种类型的惩罚只是一次性转换惩罚,反之亦然。也就是说,除非您主动混合VEX和非VEX,否则您从未对过去发生的任何事情支付持续罚款。然而,在Skylake,有一种状态,非VEX SSE指令支付高额的持续执行惩罚,即使没有进一步混合。

直接从马的嘴里,这是图11-1 1 - 旧的(前Skylake)过渡图:

正如你所看到的,所有的惩罚(红色箭头)都会带你进入一个新的状态,此时重复这个动作就不再受到惩罚了。例如,如果你通过执行一些256位AVX 进入状态,然后执行传统SSE,你需要支付一次性罚款才能转换到保留的非INIT状态,但你不付钱之后的任何处罚。

在Skylake中,根据图11-2,一切都不同:

整体惩罚较少,但对于您的情况来说,其中一个是自循环:在状态下执行传统SSE(图11-2中的惩罚A)指令的惩罚使您处于该状态。这就是你所发生的事情 - 任何AVX指令都会让你进入脏的高级状态,这会减慢所有进一步的SSE执行速度。

以下是英特尔关于新处罚的说法(第11.3节):

Skylake微体系结构实现了与前几代不同的状态机,以管理与混合SSE和AVX指令相关的YMM状态转换。在处于“已修改和未保存”状态时执行SSE指令时,它不再保存整个上YMM状态,而是保存单个寄存器的高位。结果,混合SSE和AVX指令将经历与正在使用的目的地寄存器的部分寄存器依赖性相关联的惩罚以及对目的地寄存器的高位的附加混合操作。

因此惩罚显然非常大 - 它必须始终将顶部位混合以保留它们,并且它还使得显然独立地成为依赖的指令,因为存在对隐藏的高位的依赖性。例如,xorpd xmm0, xmm0不再断开对前一个值的依赖xmm0,因为结果实际上取决于隐藏的高位,ymm0而这些高位未被清除xorpd。后一种效应可能会杀死你的表现,因为你现在拥有很长的依赖链,而这些依赖链并不是通常的分析所期望的。

这是最糟糕的性能陷阱之一:先前架构的行为/最佳实践与当前架构基本相反。据推测,硬件架构师有充分的理由进行更改,但它只会在微妙的性能问题列表中添加另一个“问题”。

我会针对插入该AVX指令但没有跟进的编译器或运行时提交错误VZEROUPPER


查看完整回答
反对 回复 2019-09-02
?
牛魔王的故事

我刚做了一些实验(在Haswell上)。干净状态和脏状态之间的转换并不昂贵,但是脏状态使得每个非VEX向量操作都依赖于目标寄存器的先前值。在您的情况下,例如, movapd %xmm1, %xmm5 将具有错误的依赖性,ymm5以防止无序执行。这解释了为什么vzeroupper在AVX代码之后需要它。


查看完整回答
反对 回复 2019-09-02
?
若吾皇

Haswell实际上是否有可能像Skylake一样,但在SKL问世之前没有人描述过这种行为?或者它有时表现得这样?在256b执行单元的上半部分加电之前的预热期间,这只是一个因素吗?在AVX-256指令缓慢的期间,状态转换行为可能不同吗?我刚买了一台SKL桌面,而且我可以使用Haswell笔记本电脑,所以我可能会有时间来测试一下。不幸的是,我无法与IvB或SnB进行比较,我认为它与您和英特尔描述的方式相同。

查看完整回答
反对 回复 2019-09-02

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信