热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

跨越AVX车道的最佳方式?

如何解决《跨越AVX车道的最佳方式?》经验,为你挑选了1个好方法。

有类似标题的问题,但我的问题涉及其他地方没有涉及的一个非常具体的用例.

我有4个__128d寄存器(x0,x1,x2,x3),我想在5 __256d寄存器(y0,y1,y2,y3,y4)中重新组合它们的内容,如下所示,准备其他计算:

on entry:
    x0 contains {a0, a1}
    x1 contains {a2, a3}
    x2 contains {a4, a5}
    x3 contains {a6, a7}
on exit:
    y0 contains {a0, a1, a2, a3}
    y1 contains {a1, a2, a3, a4}
    y2 contains {a2, a3, a4, a5}
    y3 contains {a3, a4, a5, a6}
    y4 contains {a4, a5, a6, a7}

我在下面的实现很慢.有没有更好的办法?

y0 = _mm256_set_m128d(x1, x0);

__m128d lo = _mm_shuffle_pd(x0, x1, 1);
__m128d hi = _mm_shuffle_pd(x1, x2, 1);
y1 = _mm256_set_m128d(hi, lo);

y2 = _mm256_set_m128d(x2, x1);

lo = hi;
hi = _mm_shuffle_pd(x2, x3, 1);
y3 = _mm256_set_m128d(hi, lo);

y4 = _mm256_set_m128d(x3, x2);

Peter Cordes.. 6

通过寄存器中的输入,您可以使用5个随机指令执行此操作:

3x vinsertf128通过连接2 xmm寄存器来创建y0,y2和y4.

vshufpd在这些结果之间创建y1和y3的2x (车道内改组).

请注意,y0和y2的低通道包含a1和a2,即y1的低通道所需的元素.同样的洗牌也适用于高速公路.

#include 

void merge(__m128d x0, __m128d x1, __m128d x2, __m128d x3,
     __m256d *__restrict y0, __m256d *__restrict y1,
     __m256d *__restrict y2, __m256d *__restrict y3, __m256d *__restrict y4)
{
    *y0 = _mm256_set_m128d(x1, x0);
    *y2 = _mm256_set_m128d(x2, x1);
    *y4 = _mm256_set_m128d(x3, x2);

    // take the high element from the first vector, low element from the 2nd.
    *y1 = _mm256_shuffle_pd(*y0, *y2, 0b0101);
    *y3 = _mm256_shuffle_pd(*y2, *y4, 0b0101);
}

编译非常好(与-O3 -march=haswellGodbolt 上的gcc和clang):

merge(double __vector(2), double __vector(2), double __vector(2), double __vector(2), double __vector(4)*, double __vector(4)*, double __vector(4)*, double __vector(4)*, double __vector(4)*):
    vinsertf128     ymm0, ymm0, xmm1, 0x1
    vinsertf128     ymm3, ymm2, xmm3, 0x1
    vinsertf128     ymm1, ymm1, xmm2, 0x1
    # vmovapd YMMWORD PTR [rdi], ymm0
    vshufpd ymm0, ymm0, ymm1, 5
    # vmovapd YMMWORD PTR [rdx], ymm1
    vshufpd ymm1, ymm1, ymm3, 5
    # vmovapd YMMWORD PTR [r8], ymm3
    # vmovapd YMMWORD PTR [rsi], ymm0
    # vmovapd YMMWORD PTR [rcx], ymm1
    # vzeroupper
    # ret

我评论了商店和内联中会消失的东西,所以我们确实只有5个随机指令,而不是你问题中代码的9个随机指令.(还包括在Godbolt编译器资源管理器链接中).

这在AMD上是非常好的,其中vinsertf128超便宜(因为256位寄存器实现为2x 128位半,因此它只需128位副本而无需特殊的shuffle端口.)256位通道混洗AMD的速度很慢,但是内置的256位shuffle vshufpd只有2 uops.

在英特尔它是相当不错的,但主流的带有AVX的英特尔CPU在256位或FP shuffle中每个时钟的随机吞吐量只有1个.(Sandybridge和之前的整数128位shuffle有更多的吞吐量,但AVX2 CPU减少了额外的shuffle单位,但他们无论如何都没有帮助.)

因此,英特尔CPU根本无法利用指令级并行性,但总共只有5微秒,这很不错.这是可能的最小值,因为您需要5个结果.


但特别是如果周围的代码也在洗牌时出现瓶颈,那么值得考虑的是只有4个存储和5个重叠向量加载的存储/重载策略.或者,也许2倍vinsertf128来构建y0y4,然后2个256位存储+ 3重叠的重载.这可能会让乱序exec开始依赖指令,只使用y0或者y4在为y1..3解析存储转发停顿时.

特别是如果你不太关心英特尔第一代Sandybridge,其中未对齐的256位向量负载效率较低.(注意,如果你正在使用GCC ,你需要编译gcc -mtune=haswell以关闭-mavx256-split-unaligned-load默认/ sandybridge调整.无论编译器如何,-march=native如果使二进制文件在你编译它的机器上运行是一个好主意,采取充分利用指令集和设置调整选项.)

但是如果来自前端的总uop吞吐量更多地是瓶颈所在,那么shuffle实现是最好的.

(见https://agner.org/optimize/而在其他性能链接的x86代码维基的更多性能优化.同时考虑去到什么预测现代超标量处理器的操作延迟,我怎么可以手工计算它们?,但实际上,Agner Fog的指南是一份更深入的指南,它解释了吞吐量与延迟的实际关系.)


我甚至不需要保存,因为数据也已经在连续的内存中可用.

然后简单地加载5个重叠负载几乎可以肯定是你能做的最有效的事情.

Haswell可以从L1d每个时钟执行2次加载,或者当任何跨越缓存行边界时更少.因此,如果您可以将块对齐64,那么完全没有缓存行分割就非常有效. 缓存丢失很慢,但是从L1d缓存重新加载热数据非常便宜,而具有AVX支持的现代CPU通常具有高效的非对齐负载支持.

(就像我之前说过的,如果使用gcc确保你用-march=haswell或者编译,或者-mtune=haswell不仅仅是-mavx为了避免使用gcc -mavx256-split-unaligned-load.)

4个负载+ 1 vshufpd(y0,y2)可能是平衡负载端口压力与ALU压力的好方法,具体取决于周围代码中的瓶颈.如果周围的代码在随机端口压力较低时,甚至3次加载+ 2次洗牌.


它们位于先前计算的寄存器中,需要加载它们.

如果先前的计算仍然在寄存器中有源数据,那么您可能首先完成256位加载,并且只使用它们的128位低半部分用于早期计算. (XMM寄存器是相应YMM寄存器的低128,读取它们不会干扰上面的通道,因此_mm256_castpd256_pd128编译为零asm指令.)

对y0,y2和y4执行256位加载,并将它们的低半部分用作x0,x1和x2.(稍后使用未对齐的加载或随机播放构造y1和y3).

只有x3不是你想要的256位向量的低128位.

理想情况下,当您从同一地址执行a _mm_loadu_pd和a 时,编译器已经注意到此优化_mm256_loadu_pd,但是您可能需要通过执行操作来手持它

__m256d y0 = _mm256_loadu_pd(base);
__m128d x0 = _mm256_castpd256_pd128(y0);

依此类推,提取ALU内在(_mm256_extractf128_pd)或128位负载x3,具体取决于周围的代码.如果它只需要一次,那么让它折叠成一个内存操作数,无论使用什么指令都可能是最好的.

潜在的缺点:在128位计算可以开始之前稍微延迟,或者如果256位负载是高速缓存线交叉而没有128位负载,则会有几个周期.但是如果您的数据块对齐了64个字节,则不会发生这种情况.



1> Peter Cordes..:

通过寄存器中的输入,您可以使用5个随机指令执行此操作:

3x vinsertf128通过连接2 xmm寄存器来创建y0,y2和y4.

vshufpd在这些结果之间创建y1和y3的2x (车道内改组).

请注意,y0和y2的低通道包含a1和a2,即y1的低通道所需的元素.同样的洗牌也适用于高速公路.

#include 

void merge(__m128d x0, __m128d x1, __m128d x2, __m128d x3,
     __m256d *__restrict y0, __m256d *__restrict y1,
     __m256d *__restrict y2, __m256d *__restrict y3, __m256d *__restrict y4)
{
    *y0 = _mm256_set_m128d(x1, x0);
    *y2 = _mm256_set_m128d(x2, x1);
    *y4 = _mm256_set_m128d(x3, x2);

    // take the high element from the first vector, low element from the 2nd.
    *y1 = _mm256_shuffle_pd(*y0, *y2, 0b0101);
    *y3 = _mm256_shuffle_pd(*y2, *y4, 0b0101);
}

编译非常好(与-O3 -march=haswellGodbolt 上的gcc和clang):

merge(double __vector(2), double __vector(2), double __vector(2), double __vector(2), double __vector(4)*, double __vector(4)*, double __vector(4)*, double __vector(4)*, double __vector(4)*):
    vinsertf128     ymm0, ymm0, xmm1, 0x1
    vinsertf128     ymm3, ymm2, xmm3, 0x1
    vinsertf128     ymm1, ymm1, xmm2, 0x1
    # vmovapd YMMWORD PTR [rdi], ymm0
    vshufpd ymm0, ymm0, ymm1, 5
    # vmovapd YMMWORD PTR [rdx], ymm1
    vshufpd ymm1, ymm1, ymm3, 5
    # vmovapd YMMWORD PTR [r8], ymm3
    # vmovapd YMMWORD PTR [rsi], ymm0
    # vmovapd YMMWORD PTR [rcx], ymm1
    # vzeroupper
    # ret

我评论了商店和内联中会消失的东西,所以我们确实只有5个随机指令,而不是你问题中代码的9个随机指令.(还包括在Godbolt编译器资源管理器链接中).

这在AMD上是非常好的,其中vinsertf128超便宜(因为256位寄存器实现为2x 128位半,因此它只需128位副本而无需特殊的shuffle端口.)256位通道混洗AMD的速度很慢,但是内置的256位shuffle vshufpd只有2 uops.

在英特尔它是相当不错的,但主流的带有AVX的英特尔CPU在256位或FP shuffle中每个时钟的随机吞吐量只有1个.(Sandybridge和之前的整数128位shuffle有更多的吞吐量,但AVX2 CPU减少了额外的shuffle单位,但他们无论如何都没有帮助.)

因此,英特尔CPU根本无法利用指令级并行性,但总共只有5微秒,这很不错.这是可能的最小值,因为您需要5个结果.


但特别是如果周围的代码也在洗牌时出现瓶颈,那么值得考虑的是只有4个存储和5个重叠向量加载的存储/重载策略.或者,也许2倍vinsertf128来构建y0y4,然后2个256位存储+ 3重叠的重载.这可能会让乱序exec开始依赖指令,只使用y0或者y4在为y1..3解析存储转发停顿时.

特别是如果你不太关心英特尔第一代Sandybridge,其中未对齐的256位向量负载效率较低.(注意,如果你正在使用GCC ,你需要编译gcc -mtune=haswell以关闭-mavx256-split-unaligned-load默认/ sandybridge调整.无论编译器如何,-march=native如果使二进制文件在你编译它的机器上运行是一个好主意,采取充分利用指令集和设置调整选项.)

但是如果来自前端的总uop吞吐量更多地是瓶颈所在,那么shuffle实现是最好的.

(见https://agner.org/optimize/而在其他性能链接的x86代码维基的更多性能优化.同时考虑去到什么预测现代超标量处理器的操作延迟,我怎么可以手工计算它们?,但实际上,Agner Fog的指南是一份更深入的指南,它解释了吞吐量与延迟的实际关系.)


我甚至不需要保存,因为数据也已经在连续的内存中可用.

然后简单地加载5个重叠负载几乎可以肯定是你能做的最有效的事情.

Haswell可以从L1d每个时钟执行2次加载,或者当任何跨越缓存行边界时更少.因此,如果您可以将块对齐64,那么完全没有缓存行分割就非常有效. 缓存丢失很慢,但是从L1d缓存重新加载热数据非常便宜,而具有AVX支持的现代CPU通常具有高效的非对齐负载支持.

(就像我之前说过的,如果使用gcc确保你用-march=haswell或者编译,或者-mtune=haswell不仅仅是-mavx为了避免使用gcc -mavx256-split-unaligned-load.)

4个负载+ 1 vshufpd(y0,y2)可能是平衡负载端口压力与ALU压力的好方法,具体取决于周围代码中的瓶颈.如果周围的代码在随机端口压力较低时,甚至3次加载+ 2次洗牌.


它们位于先前计算的寄存器中,需要加载它们.

如果先前的计算仍然在寄存器中有源数据,那么您可能首先完成256位加载,并且只使用它们的128位低半部分用于早期计算. (XMM寄存器是相应YMM寄存器的低128,读取它们不会干扰上面的通道,因此_mm256_castpd256_pd128编译为零asm指令.)

对y0,y2和y4执行256位加载,并将它们的低半部分用作x0,x1和x2.(稍后使用未对齐的加载或随机播放构造y1和y3).

只有x3不是你想要的256位向量的低128位.

理想情况下,当您从同一地址执行a _mm_loadu_pd和a 时,编译器已经注意到此优化_mm256_loadu_pd,但是您可能需要通过执行操作来手持它

__m256d y0 = _mm256_loadu_pd(base);
__m128d x0 = _mm256_castpd256_pd128(y0);

依此类推,提取ALU内在(_mm256_extractf128_pd)或128位负载x3,具体取决于周围的代码.如果它只需要一次,那么让它折叠成一个内存操作数,无论使用什么指令都可能是最好的.

潜在的缺点:在128位计算可以开始之前稍微延迟,或者如果256位负载是高速缓存线交叉而没有128位负载,则会有几个周期.但是如果您的数据块对齐了64个字节,则不会发生这种情况.


推荐阅读
  • vue使用
    关键词: ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文由编程笔记小编整理,主要介绍了使用Junit和黄瓜进行自动化测试中步骤缺失的问题。文章首先介绍了使用cucumber和Junit创建Runner类的代码,然后详细说明了黄瓜功能中的步骤和Steps类的实现。本文对于需要使用Junit和黄瓜进行自动化测试的开发者具有一定的参考价值。摘要长度:187字。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 本文介绍了如何使用MATLAB调用摄像头进行人脸检测和识别。首先需要安装扩展工具,并下载安装OS Generic Video Interface。然后使用MATLAB的机器视觉工具箱中的VJ算法进行人脸检测,可以直接调用CascadeObjectDetector函数进行检测。同时还介绍了如何调用摄像头进行人脸识别,并对每一帧图像进行识别。最后,给出了一些相关的参考资料和实例。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了2020年计算机二级MSOffice的选择习题及答案,详细解析了操作系统的五大功能模块,包括处理器管理、作业管理、存储器管理、设备管理和文件管理。同时,还解答了算法的有穷性的含义。 ... [详细]
  • 本文详细介绍了Python中正则表达式和re模块的使用方法。首先解释了转义符的作用,以及如何在字符串中包含特殊字符。然后介绍了re模块的功能和常用方法。通过学习本文,读者可以掌握正则表达式的基本概念和使用技巧,进一步提高Python编程能力。 ... [详细]
author-avatar
jiho_b
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有