当前位置: 首页 > 知识库问答 >
问题:

为什么gcc不将\u mm256\u loadu\u pd解析为单个vmovupd?

齐磊
2023-03-14

我正在编写一些AVX代码,需要从可能未对齐的内存中加载。我目前正在加载4个double,因此我将使用内部指令\u mm256\u loadu\u pd;我编写的代码是:

__m256d d1 = _mm256_loadu_pd(vInOut + i*4);

然后,我使用选项O3-mavx-g进行编译,然后使用objdump获得汇编程序代码以及带注释的代码和行(objdump-S-M intel-l avx.obj)
当我查看底层汇编程序代码时,我发现以下内容:

vmovupd xmm0,XMMWORD PTR [rsi+rax*1]
vinsertf128 ymm0,ymm0,XMMWORD PTR [rsi+rax*1+0x10],0x1

我本以为会看到:

vmovupd ymm0,XMMWORD PTR [rsi+rax*1]

并充分使用256位寄存器(ymm0),而gcc似乎决定填充128位部分(xmm0),然后用vinsertf128再次加载另一半。

有人能解释一下吗?
在MSVC VS 2012中,等效代码正在使用单个vmovupd编译。

我正在Ubuntu 18.04 x86-64上运行gcc(Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0


共有2个答案

张星洲
2023-03-14

GCC的通用调优拆分未对齐的256位负载以帮助旧处理器。(我相信后续更改可以避免在通用调优中拆分负载。)

您可以使用-mtune=intel-mtune=skylake之类的内容来调优更新的Intel CPU,您将按照预期获得一条指令。

危卜鹰
2023-03-14

GCC的默认调整(-mtune=generic)包括-mavx256拆分未对齐的负载-mavx256拆分未对齐的存储,因为在某些情况下,当内存在运行时实际未对齐时,这会在某些CPU(例如第一代Sandybridge和一些AMD CPU)上产生较小的加速。

使用O3-mno-avx256-split-unaligned-load-mno-avx256-split-unaligned-store如果您不想这样做,或者更好,请使用mtune=haswell。或者使用-march=native为您自己的计算机进行优化。没有“generic-avx2”调优。(https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html).

Intel Sandybridge作为单个uop运行256位加载,在加载端口中需要2个周期。(与AMD不同,AMD将所有256位向量指令解码为2个单独的uop。)Sandybridge在未对齐的256位加载方面存在问题(如果地址在运行时实际上是不对齐的)。我不知道细节,也没有找到太多关于减速确切原因的具体信息。也许是因为它使用了带有16字节库的银行缓存?但IvyBridge更好地处理256位负载,并且仍然具有银行缓存。

根据GCC邮件列表中关于实现该选项的代码的消息(https://gcc.gnu.org/ml/gcc-patches/2011-03/msg01847.html)“它将某些SPEC CPU 2006基准测试速度提高了6%。”(我认为这是针对当时唯一存在的Intel AVX CPU Sandybridge的。)

但如果在运行时内存实际上是32字节对齐的,那么即使在Sandybridge和大多数AMD CPU上,这也是一个纯粹的缺点。因此,使用此调优选项,您可能会因为未能告知编译器对齐保证而蒙受损失。如果您的循环大部分时间都在对齐的内存上运行,那么您最好至少用mno-avx256-split-unaligned-load或暗示这一点的调优选项编译该编译单元。

软件拆分一直在增加成本。让硬件处理它可以使对齐的大小写非常有效(Piledriver上的存储除外),未对齐的大小写可能比某些CPU上的软件拆分慢。因此,这是一种悲观的方法,如果数据很可能在运行时没有对齐,而不是不能保证在编译时总是对齐,那么这种方法是有意义的。e、 g.也许您有一个函数在大多数情况下使用对齐的缓冲区调用,但您仍然希望它在使用未对齐的缓冲区调用的罕见/小型情况下工作。在这种情况下,即使在Sandybridge上,拆分加载/存储策略也是不合适的。

缓冲区通常是16字节对齐的,但不是32字节对齐的,因为x86-64 glibc上的malloc(和libstdc中的new)返回16字节对齐的缓冲区(因为Alliof(maxalign_t)==16)。对于大缓冲区,指针通常是页面开始后的16个字节,因此对于大于16的对齐,它总是不对齐。改用aligned_alloc

请注意,mavx和mavx2根本没有更改调优选项:gcc-O3-mavx2仍然对所有CPU进行调优,包括那些无法实际运行AVX2指令的CPU。这是相当愚蠢的,因为如果调整为“平均AVX2 CPU”,则应该使用单个未对齐的256位负载。不幸的是,gcc没有这样做的选择,并且mavx2并不意味着mno-avx256-split-unaligned-load或任何东西。看见https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80568和https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78762对于具有指令集选择影响调整的功能请求。

这就是为什么您应该使用-游行=本机来制作本地使用的二进制文件,或者使用-游行=Sandybridge-mtune=haswell来制作可以在各种机器上运行的二进制文件,但可能主要在具有AVX的较新硬件上运行。(请注意,即使是Skylake Pentium/Celeron CPU也没有AVX或BMI2;可能在256位执行单元或寄存器文件的上半部分存在任何缺陷的CPU上,它们禁用VEX前缀的解码并将其作为低端Pentium出售。)

gcc8.2的调优选项如下。(-mar=x暗示-mtune=x)。https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html.

我通过使用-O3-fverose-ami编译并查看包含所有隐含选项的完整转储的注释来检查Goldbolt编译器资源管理器。我包含了_mm256_loadu/storeu_ps函数和一个可以自动向量化的简单浮点循环,因此我们还可以查看编译器的功能。

使用mprefer vector width=256(gcc8)或mno-prefer-avx128(gcc7及更早版本)覆盖调整选项,如mtune=bdver3,并根据需要获得256位自动矢量化,而不是仅使用手动矢量化。

  • 默认值//code>-mtune=generic:mavx256拆分未对齐的负载和存储。可以说,随着Intel Haswell越来越不合适,后来变得越来越普遍,我认为最近AMD CPU的缺点仍然很小。尤其是拆分未对齐的负载,这是AMD调优选项无法启用的
  • -march=sandybridge和-march=ivybridge:split两者。(我想我已经读到,IvyBridge改进了对未对齐的256位加载或存储的处理,因此它不太适合在运行时对齐数据的情况。)
  • 三月=哈斯韦尔及以后:均未启用拆分选项
  • 三月=knl:两个拆分选项均未启用。(Silvermont/Atom没有AVX)
  • mtune=英特尔:两个拆分选项均未启用。即使使用gcc8,与gcc8仅使用未对齐的常规策略不同,使用mtune=intel-mavx的自动矢量化也会选择达到读/写目标阵列的对齐边界。(同样,另一种情况是软件处理总是有成本的,而不是让硬件处理例外情况。)

>

  • 三月=bdver1(推土机):-mavx256拆分未对齐的存储,但不加载。它还设置gcc8等效的gcc7和更早版本的mprefer-avx128(自动矢量化将仅使用128位AVX,但当然内部函数仍可以使用256位向量)
  • 三月=bdver2(打桩机),bdver3(蒸汽压路机),bdver4(挖掘机)。与推土机相同。它们通过软件预取和足够的展开,自动向量化FP=b循环,每个缓存线只预取一次
  • 三月=znver1(Zen):-mavx256拆分未对齐的存储,但不加载,仍然只使用128位自动矢量化,但这次没有软件预取
  • 三月=btver2(AMD Fam16h,又名捷豹):均未启用拆分选项,自动矢量化,就像推土机系列一样,只有128位矢量SW预取
  • -march=eden-x4(通过带AVX2的eden):两个拆分选项均未启用,但-march选项甚至未启用-mavx,自动矢量化使用的是8字节加载,这真的很愚蠢。至少使用movsd而不是movlps来打破错误的依赖关系。但如果启用mavx,它将使用128位未对齐的加载。这里的行为非常奇怪/不一致,除非有一些奇怪的前端。

    选项(例如,作为-march=sandybridge的一部分启用,可能也适用于推土机系列(-march=bdver2是piledriver)。但是,当编译器知道内存已对齐时,这并不能解决问题。

    脚注1:AMD Piledriver有一个性能缺陷,使得256位存储吞吐量非常糟糕:根据Agner Fog的Microach pdf,即使是vmovaps[mem],ymm对齐的存储每17到20个时钟运行一个(https://agner.org/optimize/). 这种影响在推土机或蒸汽压路机/挖掘机中不存在。

    Agner Fog表示,推土机/打桩机上的256位AVX吞吐量通常比128位AVX差,部分原因是它无法解码2-2 uop模式中的指令。蒸汽压路机使256位接近盈亏平衡(如果不需要额外的洗牌)。但是寄存器寄存器vmovaps ymm指令仍然只能从推土机系列上的低128位mov消除中获益。

    但是,封闭源代码软件或二进制发行版通常没有在每个目标体系结构上使用本机构建的奢侈,因此在制作可以在任何支持AVX的CPU上运行的二进制时,需要进行权衡。只要在其他CPU上没有灾难性的缺点,在某些CPU上使用256位代码获得较大的加速通常是值得的。

    拆分未对齐的加载/存储是为了避免某些CPU上的大问题。在最近的CPU上,它会消耗额外的uop吞吐量和额外的ALU uops。但至少vinsertf128 ymm,[mem],1不需要Haswell/Skylake上端口5上的随机单元:它可以在任何向量ALU端口上运行。(而且它不会微熔断,因此它会消耗2 uops的前端带宽。)

    PS:

    大多数代码不是由最先进的编译器编译的,因此现在更改“通用”调优需要一段时间才能使用使用更新的调优编译的代码。(当然,大多数代码只使用O2或O3进行编译,而且这个选项只会影响AVX代码生成。但不幸的是,许多人使用O3-mavx2而不是O3-march=native,所以他们可能会错过FMA、BMI1/2、popcnt和其他他们的CPU支持的东西。

  •  类似资料:
    • 运行这个简单的程序时,根据编译器的不同,观察到不同的行为。 它在由GCC 11.2编译时打印,在由MSVC 19.29.30137编译时打印(两者都是截至今天的最新版本)。 相关引文(摘自最新的C 23工作草案N4901): 给定 20.15.5.4 [元一元道具], (如11.4.5.3/11[class . copy . ctor],11.4.6/9 [class.copy.assign],1

    • 是否可以根据索引范围将一行解析为多个bean 例: 行:“字段1”、“字段2”、“字段3”。。。。,“字段9”

    • 我在我的Jsoup项目中有这个有线senario 这是中的一个bug,还是标记名“”有什么特殊的地方? 注意:Jsoup版本使用了1.6和1.9。Java 7和8

    • 然而,Antlr似乎不喜欢我在两个不同的地方使用“函数”。据我所知,语法甚至没有歧义。 在下面的语法中,如果我删除第1行,生成的解析器解析示例输入没有问题。另外,如果我更改第2行或第3行中的令牌字符串,使它们不相等,解析器就会工作。 我得到的语法错误是: 测试生成的解析器的程序:

    • 问题内容: 平台之间可能有所不同,但是 当我使用gcc编译并运行下面的代码时,每次在ubuntu 11.10中获得0。 为什么即使有calloc,malloc的行为也是如此? 难道就意味着即使您不希望有时将值初始化为0,也会有不必要的性能开销吗? 编辑:哦,我以前的示例不是初始化,而是碰巧使用“新鲜”块。 我恰恰在寻找的是为什么它在分配一个大块时将其初始化: 但是,感谢您指出进行分配时存在安全原因

    • 为什么在那里插入? 如果没有一个好的理由:我能用什么方法摆脱它吗? 如果您想使用以下示例:https://godbolt.org/z/74ycy63se