Как упомянул Питер Кордес в комментариях, _mm256_rem_epu64
— это функция SVML. Большинство компиляторов не поддерживают SVML; Насколько я знаю, на самом деле это делает только ICC, но clang также можно настроить для его использования.
Единственная другая реализация 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, и clang масштабируют операцию (см. Проводник компилятора), поэтому производительность снижается. быть довольно плохим, особенно учитывая насколько медленный 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
)
);
}
(в обозревателе компиляторов)
Все это не проверено, но если предположить, что я не сделал глупых ошибок, это должно быть относительно быстрым.
Если вы действительно хотите избавиться от зависимости от libdivide, вы можете выполнить эти операции самостоятельно, но я не вижу веских причин не использовать libdivide, поэтому я оставлю это как упражнение для кого-то другого.
person
nemequ
schedule
07.07.2021
vpmullq
, как вы используете. Возможно, вы можете использовать FP с двойной точностью и обратное FP или деление FP, если это достаточно точно. - person Peter Cordes   schedule 07.07.2021