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

在运行时从simd寄存器获取任意浮点数?

如何解决《在运行时从simd寄存器获取任意浮点数?》经验,为你挑选了1个好方法。

我想从simd寄存器访问任意浮点数。我知道我可以做以下事情:

float get(const __m128i& a, const int idx){
    // editor's note: this type-puns the FP bit-pattern to int and converts to float
    return _mm_extract_ps(a,idx);
}

要么

float get(const __m128i& a, const int idx){
    return _mm_cvtss_f32(_mm_shuffle_ps(a,_MM_SHUFFLE(0,0,0,idx));
}

甚至使用轮换功能而不是随机播放。问题在于这些都需要在编译时知道idx(随机,移位和提取都需要8bit立即数)。

我也可以先使用_mm_store_ps()然后再使用结果数组来完成此操作,但这需要占用内存。有没有比这更快的方法了?

编辑:忽略第一个代码段,我希望浮点数在该位置,而不是像_mm_extract_ps返回一样的int 值。



1> Peter Cordes..:

首先,您绝对不想要_mm_extract_ps,除非您想将FP输入双关键为int1

但是无论如何,对于运行时变量索引,您可能不想分支选择具有正确的imm8的指令。

Godbolt编译器资源管理器上gcc / icc / clang / msvc的source + asm输出,用于此答案中的所有功能。 包括(在底部)一些使用编译时常量idx的测试调用程序,以便您可以看到在实际程序中进行内联+常量传播时会发生什么。和/或来自同一向量的两个索引(仅gcc CSE并从同一存储重新加载两次,其他编译器存储两次)。

存储/重新加载使用gcc / clang / ICC进行了优化(但可变idx版本的延迟较高)。 其他方法仅对带有clang的恒定输入进行了优化。(c甚至可以查看该pshufb版本并将其转换为vshufps imm8vpermilps imm8,或者将idx = 0 设为无操作)。其他编译器也会做一些愚蠢的事情,例如将向量归零vxorps并将其用作vpermilps控件!


128位向量:如果您具有SSSE3 pshufb或AVX,请使用可变随机播放

使用AVX1,您可以使用来在128位向量的2个ALU微指令中完成此操作vpermilps,这是一个使用dword选择器元素的可变混洗,与不同pshufb

这样,您就可以执行与您完全相同的随机播放_mm_shuffle_ps(包括将低元素复制到高3个元素也可以),但是使用运行时索引而不是立即数。

// you can pass vectors by value.  Not that it matters when inlining
static inline
float get128_avx(__m128i a, int idx){
    __m128i vidx = _mm_cvtsi32_si128(idx);          // vmovd
    __m128  shuffled = _mm_permutevar_ps(a, vidx);  // vpermilps
    return _mm_cvtss_f32(shuffled);
}

gcc和clang对于x86-64(Godbolt编译器资源管理器)像这样进行编译:

    vmovd           xmm1, edi
    vpermilps       xmm0, xmm0, xmm1
    ret

在没有AVX但有SSSE3的情况下,您可以为加载或创建遮罩pshufb。索引由4个__m128i向量组成的数组是相当普遍的,尤其是将_mm_movemask_ps结果用作索引。但是这里我们只关心低32位元素,因此我们可以做得更好。

实际上,模式的常规性质意味着我们可以使用两个32位立即数对它们进行乘法和加法运算。

static inline
float get128_ssse3(__m128 a, int idx) {
    const uint32_t low4 = 0x03020100, step4=0x04040404;
    uint32_t selector = low4 + idx*step4;
    __m128i vidx = _mm_cvtsi32_si128(selector);

    // alternative: load a 4-byte window into 0..15 from memory.  worse latency
    // static constexpr uint32_t shuffles[4] = { low4, low4+step4*1, low4+step4*2, low4+step4*3 };
    //__m128i vidx = _mm_cvtsi32_si128(shuffles[idx]);
    __m128i shuffled = _mm_shuffle_epi8(_mm_castps_si128(a), vidx);
    return _mm_cvtss_f32(_mm_castsi128_ps(shuffled));
}

gcc的输出-O3 -march=nehalem(其他编译器也这样做,模块可能浪费了movaps):

get128_ssse3(float __vector(4), int):
    imul    edi, edi, 67372036        # 0x04040404
    add     edi, 50462976             # 0x03020100
    movd    xmm1, edi
    pshufb  xmm0, xmm1
    ret                     # with the float we want at the bottom of XMM0

因此,如果没有AVX,则存储/重载将保存指令(和uops),尤其是在编译器可以避免对索引进行符号扩展或零扩展时。

在Core2(Penryn)及更高版本的Intel CPU上,从idx到结果的延迟= imul(3)+ add(1)+ movd(2)+ pshufb(1)。从输入向量到结果的延迟仅为pshufb。(加上Nehalem上的旁路延迟延迟。) http://agner.org/optimize/


__m256 256位向量:使用AVX2随机播放,否则可能会存储/重新加载

与AVX1不同,AVX2具有像的交叉通道随机播放 vpermps。(AVX1仅立即对整个128位通道vpermps进行了改组。)我们可以用作AVX1的vpermilps直接替代品,以从256位向量中获取元素。

有两个内部函数vpermps(请参阅Intel的内部函数查找器)。

_mm256_permutevar8x32_ps(__m256 a, __m256i idx):旧名称,操作数与asm指令相反。

_mm256_permutexvar_ps(__m256i idx, __m256 a):新名称,随AVX512一起引入,操作数的顺序正确(匹配asm操作数的顺序,与_mm_shuffle_epi8或相反_mm_permutevar_ps)。该汇编指令集参考手册条目只列出了这个版本,并列出其与错误的类型(__m256 i用于控制操作数)。

gcc和ICC仅启用AVX2而不接受AVX512接受此助记符。但是不幸的是,clang仅接受-mavx512vl(或-march=skylake-avx512),因此您不能随便使用它。因此,只需使用笨拙的8x32名称即可使用。

#ifdef __AVX2__
float get256_avx2(__m256 a, int idx) {
    __m128i vidx = _mm_cvtsi32_si128(idx);          // vmovd
    __m256i vidx256 = _mm256_castsi128_si256(vidx);  // no instructions
    __m256  shuffled = _mm256_permutevar8x32_ps(a, vidx256);  // vpermps
    return _mm256_cvtss_f32(shuffled);
}

    // operand order matches asm for the new name: index first, unlike pshufb and vpermilps
    //__m256  shuffled = _mm256_permutexvar_ps(vidx256, a);  // vpermps
#endif

_mm256_castsi128_si256 从技术上讲,它不会使上限通道保持未定义状态(因此,编译器永远不需要花费指令零扩展),但是无论如何我们都不关心上限通道。

这编译成

    vmovd   xmm1, edi
    vpermps ymm0, ymm1, ymm0
     # vzeroupper        # these go away when inlining
     # ret

因此,对于Intel CPU而言,这太棒了,从输入向量到结果只有3c的延迟,并且吞吐成本只有2 ups(但是两个upu都需要端口5)。

AMD上的过马路改组要贵得多。


存储/重新加载

存储/重新加载实际上很好的情况:

没有AVX2的256位向量,或者没有SSSE3的128位向量。

如果您需要同一向量中的2个或多个元素(但请注意,如果您实际调用gcc以外的编译器,则它们会多次存储get128_reload。因此,如果这样做,请手动内联向量存储并对其进行多次索引。)

当ALU端口压力(特别是随机端口)出现问题时,吞吐量比延迟更重要。在Intel CPU上,它movd xmm, eax也运行在端口5上,因此它与shuffle竞争。但是希望您只在内部循环之外使用标量提取,并且周围有很多代码可以完成其他工作。

when idx通常是编译时常量,您希望让编译器为您选择随机播放。

但是,坏消息idx可能会使您的程序崩溃,而不仅仅是给您错误的元素。 将索引直接转换为随机播放控件的方法将忽略高位。

注意,内联后,ICC有时会错过将常量索引优化到随机播放中。test_reload2在Godbolt示例中,ICC可以接受。

存储/重新加载到本地阵列对于吞吐量(可能不是延迟)来说是完全可以的,并且由于存储转发,在典型的CPU上只有大约6个周期的延迟。大多数CPU的前端吞吐量都比矢量ALU高,因此,如果您接近ALU吞吐量而不是存储/负载吞吐量的瓶颈,那么在混合中包含一些存储/重新加载一点也不差。

宽存储可以转发到窄重载,但要遵守一些对齐约束。我认为向量的4个或8个元素中的任何一个的自然对齐的dword重载在主流Intel CPU上都可以,但是您可以查看Intel的优化手册。请参阅x86标签Wiki中的性能链接。

在GNU C中,您可以像数组一样索引向量。如果索引不是内联后的编译时常量,则它将编译为存储/重新加载。

#ifdef __GNUC__                      // everything except MSVC
float get128_gnuc(__m128 a, int idx) {
    return a[idx]; 
    // clang turns it into idx&3
    // gcc compiles it exactly like get_reload
}
#endif

 # gcc8.1 -O3 -march=haswell
    movsx   rdi, edi                            # sign-extend int to pointer width
    vmovaps XMMWORD PTR [rsp-24], xmm0          # store into the red-zone
    vmovss  xmm0, DWORD PTR [rsp-24+rdi*4]      # reload

完全可移植的书写方式(256位版本)为:

float get256_reload(__m256 a, int idx) {
    // with lower alignment and storeu, compilers still choose to align by 32 because they see the store
    alignas(32) float tmp[8];
    _mm256_store_ps(tmp, a);
    return tmp[idx];
}

编译器需要多条指令才能使函数的独立版本中的堆栈对齐,但是当然,在内联之后,这仅会在外部包含函数中发生,希望发生在任何小循环之外。

您可以考虑将向量的上半部分/下半部分分别与vextractf128和128位存储vmovups,就像GCC _mm256_storeu_ps在不知道目标位置对齐时所做的那样,适用于tune = generic(帮助Sandybridge和AMD)。这样可以避免需要32字节的对齐阵列,而且AMD CPU基本上没有缺点。但是,如果将对齐堆栈的成本分摊到许多get()操作中,则英特尔与统一存储的情况就更糟了,因为它花费了额外的成本。(__m256有时使用函数会最终使堆栈对齐,因此您可能已经为此付出了代价。)除非仅针对Bulldozer,Ryzen和Sandybridge等进行调整,否则可能应该只使用对齐数组。


脚注1:_mm_extract_ps将FP位模式返回为int。底层的asm指令(extractps r/m32, xmm, imm8)可用于将浮点数存储到内存中,但不能将元素随机排列到XMM寄存器的底部。这是的FP版本pextrd r/m32, xmm, imm8

因此,您的函数实际上是使用编译器生成的cvtsi2ss,将整数位模式强制转换为FP ,因为C允许从intto 隐式强制转换float


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
author-avatar
coolbreeze
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有