GCC不断抱怨AVX512函数_mm512_cvt_roundpd_epi64的“错误:舍入操作数不正确”

时间:2018-08-24 04:23:52

标签: simd avx512

我正在使用_mm512_cvt_roundpd_epi64,并不断收到编译器错误,如下所示:

  

/dump/1/alicpp2/built/gcc-7.3.0-7u2/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/include/avx512dqintrin.h:1574: 14:   错误:舍入操作数不正确              __R);

这是我的代码:

    #include <iostream>
    #include <immintrin.h>

    void Date64Align(int64_t* dst, int64_t* src, size_t length) {
      constexpr int dop = 512 / 64;
      int64_t starting_epoch_milliseconds_ = 1513728000;
      int32_t granularity_milliseconds_ = 3600;

      __m512i start = _mm512_set1_epi64(starting_epoch_milliseconds_);
      __m512i granularity = _mm512_set1_epi64(granularity_milliseconds_);

      double temp = (double)granularity_milliseconds_;
      __m512d granularity_double = _mm512_set1_pd(temp);

      for (int i = 0; i < length / dop; ++i) {
        // load the src (load X into SIMD register
        __m512i data = _mm512_load_epi64(src);
        // X - starting_epoch_milliseconds_
        data = _mm512_sub_epi64(data, start);
        // convert X to double
        __m512d double_data;
        double_data = _mm512_cvt_roundepi64_pd(data, _MM_FROUND_TO_NEAREST_INT);

        // X = X / Y in double
        double_data = _mm512_div_pd(double_data, granularity_double);

        // Convert X to int64
        data = _mm512_cvt_roundpd_epi64(double_data, _MM_FROUND_NO_EXC);

        data = _mm512_mullo_epi64(data, granularity);

        // store X
        _mm512_store_epi64(dst, data);

        src += dop;
        dst += dop;
      }
    }

    int main() {
      return 0;
    }

还有我的CMakeFileLists.txt:

    cmake_minimum_required(VERSION 3.11)
    project(untitled3)

    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -msse4.2 -mavx512f - 
    mavx512dq")


    add_executable(untitled3 main.cpp)

是否有人熟悉AVX512库并可以帮助回答我的问题?

1 个答案:

答案 0 :(得分:1)

仅供参考,您通常不需要明确的四舍五入方式。默认模式为“四舍五入”,所有例外均被屏蔽。普通_mm512_cvtepi64_pd_mm512_cvtpd_epi64的行为与您所做的相同,除非您已使用fenv_MM_SET_ROUNDING_MODE更改了该线程中的默认舍入模式或异常掩码。

抑制异常只是意味着它们不会出错,但是如果我正确地阅读了英特尔的手册,它不会阻止MXCSR中相关的粘滞状态位的设置。他们说,这就像在MXCSR中设置了屏蔽位一样,并不是说它完全阻止了将异常记录在MXCSR状态位中。

_mm512_cvt_roundpd_epi64的更常见用例是通过floorceil舍入(向-/ + Infinity)舍入为整数,而不是在转换之前进行单独的舍入步骤就像您需要128位或256位向量一样。

但是,如果您运行的某些FP异常处于未屏蔽状态或可能是非默认的舍入模式,则明确的舍入取整确实有意义。


环绕模式替代必须始终包含_MM_FROUND_NO_EXC

如果编译器提供更好的错误消息告诉您,这将是很好的。 (TODO:关于gcc和clang的文件功能请求错误报告。)

({_MM_FROUND_CUR_DIRECTION不计算在内,它的意思是“无覆盖”,就像您使用了普通的非round版本的内在函数一样。)

Intel的内在函数指南指出了这一点(在the entry for _mm512_cvt_roundepi64_pd specifically中,但是您会在采用舍入模式覆盖arg的每个内在函数中找到相同的内容)

  

舍入是根据舍入参数完成的,可以是一个   的:

(_MM_FROUND_TO_NEAREST_INT |_MM_FROUND_NO_EXC) // round to nearest, and suppress exceptions
(_MM_FROUND_TO_NEG_INF |_MM_FROUND_NO_EXC)     // round down, and suppress exceptions
(_MM_FROUND_TO_POS_INF |_MM_FROUND_NO_EXC)     // round up, and suppress exceptions
(_MM_FROUND_TO_ZERO |_MM_FROUND_NO_EXC)        // truncate, and suppress exceptions
_MM_FROUND_CUR_DIRECTION // use MXCSR.RC; see _MM_SET_ROUNDING_MODE

请注意,_MM_FROUND_NO_EXC本身是有效的,因为_MM_FROUND_TO_NEAREST_INT恰好是0,与2位舍入模式字段的机器代码编码相同EVEX.b已设置。但是您应该在_mm512_cvt_roundpd_epi64中明确指出这一点。

对于没有舍入控制的指令,例如_mm512_cvtt_roundpd_epi64(请注意多余的t用于截断),仅允许_MM_FROUND_NO_EXC(或_MM_FROUND_CUR_DIRECTION)使用,因为该行为不受2位字段值的影响,所以无论是否指定了舍入替代都可以。


在EVEX前缀的机器编码中,设置舍入模式替代表示SAE(禁止所有异常)。在不抑制异常的情况下,无法编码_MM_FROUND_TO_NEAREST_INT覆盖。

From Intel's vol.2 instruction set reference manual

  

2.6.8 EVEX中的静态舍入支持

     

EVEX编码系统中嵌入的静态舍入控制适用   仅用于注册到注册的浮点指令   在两个不同的向量长度处具有舍入语义:(i)标量,   (ii)512位。在这两种情况下,字段EVEX.L’L均表示舍入   如果设置了MXCSR.RC,则模式控制将覆盖EVEX.b设置了EVEX.b后,   暗示“禁止所有例外”。

请注意,舍入覆盖使编译器无法使用内存源操作数,因为在该上下文中,EVEX.b位表示广播与非广播。

这对您来说不是问题;数据来自_mm512_sub_epi64,但值得一提的是,通常情况下,舍入模式的替代已经是默认值,在某些情况下可能需要额外的加载指令,从而对性能造成较小的影响被需要。不过,静态舍入总是比额外的_mm512_roundscale_pd好(instrinsic _mm512_round_ps is missing for AVX512)。


顺便说一句,这些限制(仅适用于标量或512位向量,并且仅适用于非内存指令)是AVX512完全具有vcvttpd2qq而不是仅使用_MM_FROUND_TO_ZERO|_MM_FROUND_NO_EXC的原因_mm512_cvt_roundpd_epi64。因为没有_mm256_cvt_roundpd_epi64,所以如果编译器可以将负载折叠到vcvttpd2qq的内存操作数中,则有时会很好。

还有一个历史先例:自从SSE1 cvttss2sicvttps2dq以来,英特尔已经进行了截断转换,这使得 更加有效地实现了C的FP-> int强制转换语义在SSE3 fisttp之前,以以前使用x87更改MXCSR舍入模式的方式。

在AVX512之前,从未支持过涉及64位整数的打包转换,因此该指令不存在128位或256位版本。不过,提供一个很好的设计决定。

舍入替代是AVX512中的新增功能。在此之前,使用SSE4.1 __m128 / __m128d可以使用显式模式进行打包舍入到整数(输入和输出均为roundpsroundpd)。


其他实现以提高效率:

添加而不是子

__m512i minus_start = _mm512_set1_epi64(-starting_epoch_milliseconds_);

 for(){ 
    __m512i data = _mm512_add_epi64(data, minus_start);
 }

add是可交换的,因此编译器可以将负载折叠为vpaddq zmm0, zmm8, [rdi]之类的load + add指令,而不是单独的load + sub。 clang为您做了优化,但是gcc doesn't


您似乎想将输入整数舍入到3600的最接近倍数。

用除法代替除法

1.0/3600舍入到最接近的double大约为2.777777777777777775368439616699e-04,这在2 ^ 53(the significand precision of double)中最多只错了0.5个部分。大约是10 ^ -16。对于小于此值的输入,lrint(x * (1.0/3600))lrint(x / 3600.0)的1之内。对于大多数合理大小的输入,它们是完全相等的。

乘法之后,您仍然总是得到3600的精确倍数,但是“除法”中的微小错误最终会使您偏离3600倍。

您可以编写一个测试程序来查找通过除法与乘以逆得到不同结果的情况。


您可以在另一次传递数据时执行此操作吗?对于所有内存带宽而言,这并不是很多计算。或者,如果您不能用乘数乘以倒数来代替div_pd,那么它就完全成为FP划分的瓶颈,而又不会使其他执行单元处于忙碌状态。

此处提供三种策略:

  • 纯整数,使用乘法逆进行精确除法。 Why does GCC use multiplication by a strange number in implementing integer division?。 Evan AVX512DQ没有一个整数乘法,可以为您提供64x64 => 128的 high 一半,而只有vpmullq 64x64 => 64位(并且是多次)。

    没有AVX512IFMA VPMADD52HUQ(52x52 => 52位乘法的高半部分),请参阅Can I use the AVX FMA units to do bit-exact 52 bit integer multiplications?。 (或者,如果您实际上只关心输入的低32位,则应该使用_mm512_mul_epu32和单位vpmuludq使用32x32 => 64位乘法和64位移位。)还需要进行额外的工作以四舍五入到最接近的位置,而不是舍入。

  • 您现在要做什么:double除(或乘以逆),转换为最接近的int64_t,64位整数乘。

    如果> 2 ^ 53,则输入可能会四舍五入到最接近的double,但是最终结果将始终是3600的精确倍数(除非乘积溢出int64_t)。

  • double除(或乘),四舍五入到最接近的整数(不转换),double乘,转换成整数。

    如果是above 2^(53+4),则最后一个乘法的结果可能是一个问题。 3600是2 ^ 4的倍数,但不是2 ^ 5的倍数。因此,对于非常大的输入,四舍五入到最接近的可表示double可能会得出的数字不是3600的整数倍。

    如果范围限制不是问题,您甚至可以使用fma(val, 3600, -3600.0*start)将减法折叠起来。

    SIMD FP乘法的吞吐量显着高于整数乘法,因此,即使FP舍入至最接近指令的额外成本,它也可能是总体上的胜利。

有时,您可以通过添加然后减去一个大常量来避免显式舍入指令,就像Can I use the AVX FMA units to do bit-exact 52 bit integer multiplications?中的@Mysticial那样。您使值足够大,以使其最接近的可表示double是整数。 (How to efficiently perform double/int64 conversions with SSE/AVX?,用于有限范围的输入,还显示了一些FP操作技巧。)

也许我们可以rounded=fma(v, 1.0/3600, round_constant),然后减去round_constant以获得不带_mm512_roundscale_pd的四舍五入到最接近的整数的值。我们甚至可以将fma(rounded, 3600, -3600*round_constant)折叠起来以进行缩放:2 ^ 52 * 3600 = 4503599627370496.0 * 3600可以精确地表示为double

可能存在双舍入问题:首先是从int64_t转换为最接近的double(如果它太大以至于整数不能精确表示),然后又是除法和舍入到最接近的整数。


费用:我假设您可以用1.0/3600乘以FP来除法。

  • fp mul,转换为整数mul:vcvtqq2pd(用于FMA端口的1 uop)+ vmulpd(1 uop)+ vcvtpd2qq(1 uop)+ {{1 }}(对于FMA端口为3 uops)对于2个FMA端口为6 uops。 vpmullq也争夺相同的端口,所以实际上是7。 SKX uop从Agner Fog's testing开始计数。

  • fp一切:vpsubq zmm(FMA端口1 uop)+ vcvtqq2pd(1 uop)+ vmulpd(2 uop)+ vrndscalepd(1 uop )+ vmulpd(1 uop)= 6 oups,但可能会降低延迟。 (vrndscale + vmulpd为8 + 4延迟,比vpmullq 15周期延迟更快)。但是OoO执行人员如果在数组上循环以获取独立矢量,则应该轻松隐藏此延迟,因此节省延迟并不是一件大事。

我不确定您可以使“整数”乘法或使用FP bithack避免转换指令的效率如何。如果这对性能至关重要,那么可能值得研究。