_mm256_rem_epu64 ไม่พบภายในกับ GCC 10.3.0

ฉันพยายามเขียนการคูณเมทริกซ์ uint64_t 2x2 ต่อไปนี้อีกครั้งด้วยคำสั่ง AVX-512 แต่ GCC 10.3 ไม่พบ _mm256_rem_epu64 ที่อยู่ภายใน

#include <cstdint>
#include <immintrin.h>

constexpr uint32_t LAST_9_DIGITS_DIVIDER = 1000000000;

void multiply(uint64_t f[2][2], uint64_t m[2][2])
{
  uint64_t x = (f[0][0] * m[0][0] + f[0][1] * m[1][0]) % LAST_9_DIGITS_DIVIDER;
  uint64_t y = (f[0][0] * m[0][1] + f[0][1] * m[1][1]) % LAST_9_DIGITS_DIVIDER;
  uint64_t z = (f[1][0] * m[0][0] + f[1][1] * m[1][0]) % LAST_9_DIGITS_DIVIDER;
  uint64_t w = (f[1][0] * m[0][1] + f[1][1] * m[1][1]) % LAST_9_DIGITS_DIVIDER;

  f[0][0] = x;
  f[0][1] = y;
  f[1][0] = z;
  f[1][1] = w;
}

void multiply_simd(uint64_t f[2][2], uint64_t m[2][2])
{
  __m256i v1 = _mm256_set_epi64x(f[0][0], f[0][0], f[1][0], f[1][0]);
  __m256i v2 = _mm256_set_epi64x(m[0][0], m[0][1], m[0][0], m[0][1]);
  __m256i v3 = _mm256_mullo_epi64(v1, v2);

  __m256i v4 = _mm256_set_epi64x(f[0][1], f[0][1], f[1][1], f[1][1]);
  __m256i v5 = _mm256_set_epi64x(m[1][0], m[1][1], m[1][0], m[1][1]);
  __m256i v6 = _mm256_mullo_epi64(v4, v5);

  __m256i v7 = _mm256_add_epi64(v3, v6);
  __m256i div = _mm256_set1_epi64x(LAST_9_DIGITS_DIVIDER);
  __m256i v8 = _mm256_rem_epu64(v7, div);
  _mm256_store_epi64(f, v8);
}

เป็นไปได้ไหมที่จะเปิดใช้งาน _mm256_rem_epu64 หรือหากไม่ใช่ มีวิธีอื่นในการคำนวณการเตือนด้วยคำแนะนำ SIMD


person bobeff    schedule 07.07.2021    source แหล่งที่มา
comment
นั่นคือ ฟังก์ชัน SVML ไม่ใช่สิ่งที่อยู่ภายในสำหรับ คำสั่ง CPU อย่าใช้การหารรันไทม์ ให้ใช้การผกผันการคูณ (เช่นเดียวกับใน วิธีที่เร็วที่สุดในการสร้างไฟล์ข้อความขนาด 1 GB ที่มีตัวเลขสุ่มคืออะไร เหมือนที่ฉันทำกับจำนวนเต็ม 16 บิต . เหตุใด GCC จึงใช้การคูณด้วยจำนวนแปลกในการหารจำนวนเต็ม เป็นเคล็ดลับทั่วไป)   -  person Peter Cordes    schedule 07.07.2021
comment
ฉันคิดว่า AVX-512 มีการคูณครึ่งสูง 64 บิต แต่บางทีนั่นอาจเป็นเพียงกับ AVX512-IFMA52 เท่านั้น (felixcloutier.com/x86/vpmadd52huq) สำหรับการคูณแบบ 64 บิต ฉันจะเห็นเฉพาะ vpmullq เหมือนที่คุณใช้อยู่เท่านั้น บางทีคุณอาจใช้ FP ที่มีความแม่นยำสองเท่าและ FP ผกผัน หรือการหาร FP ได้ ถ้าแม่นยำเพียงพอ   -  person Peter Cordes    schedule 07.07.2021


คำตอบ (1)


ดังที่ Peter Cordes กล่าวถึงในความคิดเห็น _mm256_rem_epu64 เป็นฟังก์ชัน SVML คอมไพเลอร์ส่วนใหญ่ไม่รองรับ SVML; AFAIK จริงๆ แล้วมีเพียง ICC เท่านั้นที่ทำได้ แต่เสียงดังกราวสามารถกำหนดค่าให้ใช้งานได้เช่นกัน

การใช้งาน SVML อื่นๆ เพียงอย่างเดียวที่ฉันทราบคือหนึ่งในโปรเจ็กต์ของฉัน SIMDe ในกรณีนี้ เนื่องจากคุณใช้ GCC 10.3 การใช้งาน _mm256_rem_epu64 จะใช้ ส่วนขยายเวกเตอร์ ดังนั้น โค้ดจาก SIMDe โดยพื้นฐานแล้วจะเหมือนกับบางอย่างเช่น:

#include <immintrin.h>
#include <stdint.h>

typedef uint64_t u64x4 __attribute__((__vector_size__(32)));

__m256i
foo_mm256_rem_epu64(__m256i a, __m256i b) {
    return (__m256i) (((u64x4) a) % ((u64x4) b));
}

ในกรณีนี้ ทั้ง GCC และเสียงดังกราวจะปรับขนาดการดำเนินการ (ดู Compiler Explorer) ดังนั้นประสิทธิภาพจึงดำเนินต่อไป ค่อนข้างแย่ โดยเฉพาะอย่างยิ่ง เมื่อพิจารณาว่า ช้าแค่ไหน div การเรียนการสอนคือ

กล่าวคือ เนื่องจากคุณใช้ค่าคงที่เวลาคอมไพล์ คอมไพลเลอร์ควรจะสามารถแทนที่การหารด้วยการคูณและกะได้ ดังนั้นประสิทธิภาพจะดีกว่า แต่เราสามารถบีบเพิ่มเติมได้อีกโดยใช้ libdivide

โดยปกติ Libdivide จะคำนวณค่าเวทย์มนตร์ ณ รันไทม์ แต่ libdivide_u64_t< /a> โครงสร้างนั้นง่ายมาก และเราสามารถข้ามขั้นตอน libdivide_u64_gen และจัดเตรียมโครงสร้าง ณ เวลาคอมไพล์ได้:

__m256i div_by_1000000000(__m256i a) {
    static const struct libdivide_u64_t d = {
        UINT64_C(1360296554856532783),
        UINT8_C(93)
    };
    return libdivide_u64_do_vec256(a, &d);
}

ตอนนี้ หากคุณสามารถใช้ AVX-512VL + AVX-512DQ ได้ ก็จะมีฟังก์ชันการคูณแบบ 64 บิต (_mm256_mullo_epi64) หากคุณสามารถใช้ได้มันอาจเป็นวิธีที่ถูกต้อง:

__m256i rem_1000000000(__m256i a) {
    static const struct libdivide_u64_t d = {
        UINT64_C(1360296554856532783),
        UINT8_C(93)
    };
    return
        _mm256_sub_epi64(
            a,
            _mm256_mullo_epi64(
                libdivide_u64_do_vec256(a, &d),
                _mm256_set1_epi64x(1000000000)
            )
        );
}

(หรือบน Compiler Explorer พร้อมด้วย LLVM-MCA)

หากคุณไม่มี AVX-512DQ+VL ​​คุณอาจต้องการกลับไปใช้ส่วนขยายเวกเตอร์อีกครั้ง:

typedef uint64_t u64x4 __attribute__((__vector_size__(32)));

__m256i rem_1000000000(__m256i a) {
    static const struct libdivide_u64_t d = {
        UINT64_C(1360296554856532783),
        UINT8_C(93)
    };
    u64x4 one_billion = { 1000000000, 1000000000, 1000000000, 1000000000 };
    return (__m256i) (
        (
            (u64x4) a) -
            (((u64x4) libdivide_u64_do_vec256(a, &d)) * one_billion
        )
    );
}

(บน Compiler Explorer)

ทั้งหมดนี้ยังไม่ผ่านการทดสอบ แต่สมมติว่าฉันไม่ได้ทำผิดพลาดโง่ๆ มันควรจะค่อนข้างเร็ว

หากคุณต้องการกำจัดการพึ่งพา libdivide จริงๆ คุณสามารถดำเนินการเหล่านั้นได้ด้วยตัวเอง แต่ฉันไม่เห็นเหตุผลที่ดีเลยที่จะไม่ใช้ libdivide ดังนั้นฉันจะปล่อยให้มันเป็นแบบฝึกหัดสำหรับคนอื่น

person nemequ    schedule 07.07.2021