【问题标题】:Horizontal add with __m512 (AVX512)使用 __m512 (AVX512) 水平添加
【发布时间】:2015-01-09 20:43:31
【问题描述】:

如何在 512 位 AVX 寄存器中有效地使用浮点数执行水平加法(即将单个向量中的项目加在一起)?对于 128 位和 256 位寄存器,这可以使用 _mm_hadd_ps 和 _mm256_hadd_ps 完成,但没有 _mm512_hadd_ps。英特尔内在函数指南文档 _mm512_reduce_add_ps。它实际上并不对应于单个指令,但它的存在表明存在一种最佳方法,但它似乎没有在 GCC 最新快照附带的头文件中定义,我找不到它与谷歌。

我认为“hadd”可以用 _mm512_shuffle_ps 和 _mm512_add_ps 来模拟,或者我可以使用 _mm512_extractf32x4_ps 将一个 512 位寄存器分解为四个 128 位寄存器,但我想确保我不会错过更好的东西。

【问题讨论】:

  • 你到底想用水平操作做什么?如果它是大型归约操作的结束,那么它甚至可能不是性能关键的。 (尽管如此,_mm512_reduce_add_ps 是为此目的而存在的,并编译为 shuffle 和 sum 的二进制减少。)
  • 我并不感到惊讶,因为 AVX-512 有点偏离标准的“双倍宽度”改进。操作已经被分割成 128 位或 256 位微指令,所以水平指令还没有多大意义。
  • @CoryNelson 更糟糕的是,水平指令在现有处理器上进行了微编码。所以他们已经很慢了。而且,水平矢量化的任务违反了 SIMD 范式并且无法扩展。
  • @Mystical 水平操作仅在 AMD Bulldozer/Piledriver/Steamroller 上进行微编码
  • @MaratDukhan 根据 Agner Fog 的表格,它们还在 Prescott、Core 2、Nehalem、Sandy Bridge、Haswell、Atom 和 Via Nano 上进行了微编码。这几乎涵盖了其他所有内容。他没有关于K10的任何信息。 K8 的条目是空白的。

标签: simd intrinsics avx512


【解决方案1】:

INTEL 编译器定义了以下内在函数来进行水平求和

_mm512_reduce_add_ps     //horizontal sum of 16 floats
_mm512_reduce_add_pd     //horizontal sum of 8 doubles
_mm512_reduce_add_epi32  //horizontal sum of 16 32-bit integers
_mm512_reduce_add_epi64  //horizontal sum of 8 64-bit integers

但是,据我所知,这些指令无论如何都被分解为多条指令,所以我认为除了对 AVX512 寄存器的上下部分进行水平总和之外,你没有什么收获。

__m256 low  = _mm512_castps512_ps256(zmm);
__m256 high = _mm256_castpd_ps(_mm512_extractf64x4_pd(_mm512_castps_pd(zmm),1));

__m256d low  = _mm512_castpd512_pd256(zmm);
__m256d high = _mm512_extractf64x4_pd(zmm,1);

__m256i low  = _mm512_castsi512_si256(zmm);
__m256i high = _mm512_extracti64x4_epi64(zmm,1);

要获得水平总和,请执行sum = horizontal_add(low + high)

static inline float horizontal_add (__m256 a) {
    __m256 t1 = _mm256_hadd_ps(a,a);
    __m256 t2 = _mm256_hadd_ps(t1,t1);
    __m128 t3 = _mm256_extractf128_ps(t2,1);
    __m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2),t3);
    return _mm_cvtss_f32(t4);        
}

static inline double horizontal_add (__m256d a) {
    __m256d t1 = _mm256_hadd_pd(a,a);
    __m128d t2 = _mm256_extractf128_pd(t1,1);
    __m128d t3 = _mm_add_sd(_mm256_castpd256_pd128(t1),t2);
    return _mm_cvtsd_f64(t3);        
}

我从Agner Fog's Vector Class LibraryIntel Instrinsics Guide online 获得了所有这些信息和功能。

【讨论】:

  • 您确定没有提取高 256 内在函数的 _ps 版本吗?在那里投射到_pd 似乎真的很奇怪。但是,是的,一个好的第一步是提取高 256 和垂直添加。但是然后做同样的事情到 128,然后使用比 vhaddps 更好的随机播放,这需要 2 次随机播放 + 一个垂直添加。见stackoverflow.com/questions/6996764/…
  • 我通常更喜欢使用直接的 reduce_add 内在函数,因为它清楚地向代码的人类读者和编译器表达了意图,当它知道你在做什么时,它通常会更好地优化真的很想做。
【解决方案2】:

我会给 Z boson 支票,因为帖子确实回答了我的问题,但我认为可以改进指令的确切顺序:

inline float horizontal_add(__m512 a) {
    __m512 tmp = _mm512_add_ps(a,_mm512_shuffle_f32x4(a,a,_MM_SHUFFLE(0,0,3,2)));
    __m128 r = _mm512_castps512_ps128(_mm512_add_ps(tmp,_mm512_shuffle_f32x4(tmp,tmp,_MM_SHUFFLE(0,0,0,1))));
    r = _mm_hadd_ps(r,r);
    return _mm_cvtss_f32(_mm_hadd_ps(r,r));
}

【讨论】:

  • 很高兴您找到了更适合您的解决方案。您可以获得适用于 Linux 的英特尔编译器的免费非商业版本,然后您可以查看反汇编以了解它对 _mm512_reduce 的作用。但是您应该记住,您应该在关键循环中进行水平添加。它违背了 SIMD 的目的。
  • @Zboson 英特尔编译器的免费版本会很好,但是当我进入英特尔网站的非商业软件开发部分时,它只有一页说“这个网站正在修订中”。这种情况已经有一段时间了。至于横向加注:我知道,但在无法避免的情况下,总比16个数字加起来好,一次加一个。而且我不是在尝试优化单个操作;我有一个特殊的数组 (C++) 类,它隐藏了我正在尝试优化的所有 SIMD 代码(这也是我的向量类的基础)。
  • 那是个坏消息。我不知道非商业软件版本是“正在修订”。好吧,如果有什么安慰的话,我认为 ICC 被高估了,除了它的库(例如 MKL)非常好。
  • 现在我看到这个我不确定它是否比我的解决方案更好。您和我的解决方案使用六个指令(忽略_mm_cvtss_f32)。您刚刚编写了解决方案,它看起来更短,因为您每行打包了多个内在函数。不过,您的解决方案仍然很有趣。
  • 但是您的解决方案调用了 Horizo​​ntal_add(__m256) 两次。假设调用是内联的,那么总共有 10 条指令。
【解决方案3】:

双精度水平总和:

static inline double _mm512_horizontal_add(__m512d a){
    __m256d b = _mm256_add_pd(_mm512_castpd512_pd256(a), _mm512_extractf64x4_pd(a,1));
    __m128d d = _mm_add_pd(_mm256_castpd256_pd128(b), _mm256_extractf128_pd(b,1));
    double *f = (double*)&d;
    return _mm_cvtsd_f64(d) + f[1];
}

编辑:Peter Cordes 的应用 cmets

【讨论】:

  • 我不推荐 hadd_pd:它需要 2 次随机播放 + 1 次添加 uops,而不是手动提取只需 1 次随机播放。此外,您正在使用 + 运算符,它是 GNU C 本机向量扩展。您还依赖__m512i 的gcc/clang 定义作为long long 的向量,因此+_mm256_add_epi64,而不是其他整数宽度。我认为这不会改变,但它通常不是很好的风格,IMO。
  • 良好的编辑,直到从指针转换中严格混叠未定义的行为。只需像正常人一样使用另一个洗牌,例如_mm_unpackhi_pd,而不是诱使编译器溢出 d 并进行标量重新加载。如果你想要一个标量+,你可以_mm_cvtsd_f64 两半,或者使用_mm_add_sd_pdFastest way to do horizontal float vector sum on x86 展示了一个使用 movhlps 的 hack,如果没有 AVX,这可能是值得的,但使用 AVX 来避免 movaps 复制毫无意义。请参阅该答案中的highhalf_pd
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-09
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多