Самый быстрый способ инициализации константы __m128i с помощью встроенных функций? - PullRequest
0 голосов
/ 20 марта 2020

В настоящее время у меня есть переменная __m128i, назовем ее X. Я хочу xor с постоянным 128-битным значением и сохранить значение обратно в X. Итак, по существу X ^= C для некоторой константы C.

В настоящее время я делаю что-то вроде:

X = _mm_xor_si128(X, _mm_set_epi64x(C_a, C_b))

, который создает __m128i из двух 64-битных частей C для xor.

Мой вопрос, похоже, это не самый эффективный способ инициализации константы __m128i для xor. Было бы лучше попытаться сделать некоторую загрузку из выровненного массива? Или какой-то другой метод?

В настоящее время я работаю с MSV C в Visual Studio.

1 Ответ

2 голосов
/ 20 марта 2020

Этот ответ чисто о случае постоянной C. Если у вас есть неконстантные входные данные, важно, откуда они берутся (память, регистры, недавние вычисления, которые вы, возможно, могли бы в первую очередь выполнять в векторных регистрах?) И, возможно, что вы делаете с результирующим вектор. Перетасовывание отдельных скалярных переменных в / из SIMD-векторов своего рода отстой, с компромиссом между узкими местами порта ALU в сравнении с задержкой и пропускной способностью сохранения / перезагрузки (и задержки пересылки магазина для скалярного -> вектора). Функция сохранения / перезагрузки хороша для получения множества мелких элементов из вектора SIMD, когда вы хотите их все.


Для констант C_a и C_b, даже MSV C хорошо справляется с постоянным распространением через это _mm_set. Таким образом, нет никакого преимущества в написании инициализирующего c инициализатора, такого как Ошибка SSE - Использование m128i_i32 для определения полей переменной __m128i

Помните, что реальный определитель производительности сборка, которую вы можете заставить компилятор создавать, но не то, какие встроенные функции вы используете для этого.

#include <immintrin.h>

__m128i xor_const(__m128i v) {
    return _mm_xor_si128(v, _mm_set_epi64x(0x789abc, 0x123456));
}

Скомпилировано ( на Godbolt ) с помощью x64 MSV C -O2 Gv (чтобы использовать vectorcall, чтобы мы могли видеть, что он делает, когда вектор уже находится в регистре, например, когда он встроен), мы получаем этот довольно глупый ассемблер, который, надеюсь, не будет таким плохим в большая функция после встраивания:

;; MSVC 19.10
;; this is in the .rdata section; godbolt just filters directives that aren't interesting
;; "everyone knows" that compilers put data in the right sections
__xmm@0000000000789abc0000000000123456 DB 'V4', 012H, 00H, 00H, 00H, 00H, 00H
        DB      0bcH, 09aH, 'x', 00H, 00H, 00H, 00H, 00H

xor_const@@16 PROC                                  ; COMDAT
        movdqa  xmm1, XMMWORD PTR __xmm@0000000000789abc0000000000123456
        pxor    xmm1, xmm0
        movdqa  xmm0, xmm1
        ret     0
xor_const@@16 ENDP

Мы можем видеть, что _mm_set intrinsi c скомпилирован в 16-байтовую константу в хранилище stati c, как мы и хотим. Отказ от использования pxor xmm0, xmm1 удивителен, но MSV C хорошо известен для asm, который часто не так хорош по сравнению с G CC и / или лязгом. Опять же, как часть большой функции, когда у нее есть выбор регистров, у нас, вероятно, не будет лишних movdqa. И если xor был в al oop, то загрузка в любом случае за пределами al oop - это то, что нам нужно. Это была не самая последняя версия MSV C; Godbolt имеет только самые последние версии MSV C, установленные для C ++, а не C, но вы пометили это C.


Для сравнения, GCC9.2 -O3 компилируется к ожидаемому источнику памяти PXOR, который эффективен на всех процессорах.

xor_const:
        pxor    xmm0, XMMWORD PTR .LC0[rip]
        ret

.section .rodata    # Godbolt strips out stuff like section directive; re-added manually
.LC0:
        .quad   1193046
        .quad   7903932

Возможно, вы могли бы получить компилятор для генерации того же ассемблера с массивом stati c alignas(16), содержащим константу, и _mm_load_si128() от этого. Но зачем?

Одна вещь, которую следует избегать пишет: static const __m128i C = _mm_set... - компиляторы очень глупы и не будут складывать _mm_set в состояние c постоянный инициализатор для __m128i. C компиляторы откажутся компилировать не константный инициализатор stati c. Компиляторы C ++ зарезервируют некоторое пространство BSS и запустят функцию, подобную конструктору, для копирования из постоянной только для чтения в это пространство BSS, потому что _mm_set не полностью оптимизирует в этом случае.

...