Доступ к произвольным 16-битным элементам, упакованным в 128-битный регистр - PullRequest
7 голосов
/ 01 апреля 2012

Благодаря встроенным функциям компилятора Intel с 128-битным регистром, упаковывающим 8 16-битных элементов, как я могу (дешево) получить доступ к произвольным элементам из регистра для последующего использования _mm_cvtepi8_epi64 (знак расширяет два 8-битных элемента)элементы, упакованные в младших 16 битах регистра, в два 64-битных элемента)?


Я объясню, почему я спрашиваю:

  1. Вход: вход в-память памяти с k байтами, каждый из которых 0x0 или 0xff.
  2. Желаемый вывод: для каждых двух последовательных байтов ввода регистр, упаковывающий два четырехзначных слова (64 бита) с 0x0 и 0xffff ffff ffff ffffсоответственно.
  3. Конечная цель: суммировать буфер из k удваивается, маскируется в соответствии с записями входного буфера.

Примечание: значения 0x0 и 0xff извходной буфер может быть изменен на тот, который наиболее полезен, при условии, что эффект маскирования до суммирования сохраняется.

Как может быть видно из моего вопроса, мой текущий план выглядит следующим образом, проходя через входные буферы:

  1. Ex Расширьте буфер входной маски от 8 до 64 бит.
  2. Маска буфера двойников с расширенной маской.
  3. Суммирование маскированных двойников.

СпасибоАсаф

Ответы [ 3 ]

3 голосов
/ 05 апреля 2012

Скорее касательная к самому вопросу, больше заполняется некоторая информация о комментариях, потому что сам раздел комментариев слишком мал, чтобы вместить это ( sic! ):

Как минимум, gccможет иметь дело со следующим кодом:

#include <smmintrin.h>

extern int fumble(__m128i x);

int main(int argc, char **argv)
{
    __m128i foo;
    __m128i* bar = (__m128i*)argv;

    foo = _mm_cvtepi8_epi64(*bar);

    return fumble(foo);
}

Это превращает это в следующую сборку:

Disassembly of section .text.startup:

0000000000000000 :
   0:   66 0f 38 22 06          pmovsxbq (%rsi),%xmm0
   5:   e9 XX XX XX XX          jmpq   .....

Это означает, что встроенные не должны входить в память.форма аргумента - компилятор обрабатывает разыменование аргумента mem прозрачно и, если возможно, использует соответствующую инструкцию mem-операнда.ICC делает то же самое.У меня нет машины с Windows / Visual C ++, чтобы проверить, работает ли MSVC так же, но я ожидаю, что это так.

3 голосов
/ 05 июня 2015

Каждый байт является маской для всего двойного числа, , поэтому PMOVSXBQ делает именно то, что нам нужно : загружает два байта из указателя m16 и расширяет их до двух 64-битных (qword) ) половины регистра xmm.

# UNTESTED CODE
# (loop setup stuff)
# RSI: double pointer
# RDI: mask pointer
# RCX: loop conter = mask byte-count
    add   rdi, rcx
    lea   rsi, [rsi + rcx*8]  ; sizeof(double) = 8
    neg   rcx  ; point to the end and count up

    XORPS xmm0, xmm0  ; clear accumulator
      ; for real use: use multiple accumulators
      ; to hide ADDPD latency

ALIGN 16
.loop:
    PMOVSXBQ XMM1, [RDI + RCX]
    ANDPD    XMM1, [RSI + RCX * 8]
    ADDPD    XMM0, XMM1
    add      RCX, 2      ; 2 bytes / doubles per iter
    jl       .loop

    MOVHLPS  XMM1, XMM0    ; combine the two parallel sums
    ADDPD    XMM0, XMM1 
    ret

Для реального использования используйте несколько аккумуляторов. Также см. Режимы микросинтеза и адресации re: индексированные режимы адресации.

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


Чтобы ответить на другую часть вашего вопроса, о , как сдвинуть данные, чтобы выстроить их в соответствие для PMOVSX:

В Sandybridge и более поздних версиях использование PMOVSXBQ из ОЗУ, вероятно, хорошо. На более ранних процессорах, которые не могут обрабатывать две загрузки за цикл, загрузка 16B данных маски за раз и смещение их на 2 байта за раз с PSRLDQ xmm1, 2 поместит 2 байта данных маски в младшие 2 байта регистра , Или, может быть, PUNPCKHQDQ, или PSHUFD, чтобы получить две цепочки зависимостей, перемещая максимум 64 в минимум 64 другого регистра. Вам нужно проверить, какой порт используется какой инструкцией (сдвиг вместо случайного / извлечения), и посмотреть, какой из них меньше конфликтует с PMOVSX и ADDPD.

punpck и pshufd оба используют p1 / p5 на SnB, как и pmovsx. addpd может работать только на p1. andpd может работать только на p5. Хм, может быть, PAND будет лучше, так как он может работать на p0 (и p1 / p5). Иначе ничто в цикле не будет использовать порт выполнения 0. Если есть штраф за задержку при перемещении данных из целочисленных доменов в домены fp, это неизбежно, если мы используем PMOVSX, так как это получит данные маски в домене int. Лучше использовать больше аккумуляторов, чтобы сделать цикл длиннее, чем самая длинная цепочка зависимостей. Но держите его под 28 моп или около того, чтобы поместиться в буфер цикла, чтобы гарантировать, что 4 мопа могут выдать за цикл.

И еще об оптимизации всего этого: Выравнивание цикла на самом деле не требуется, так как в Nehalem и более поздних версиях он помещается в буфер цикла.

Вы должны развернуть цикл на 2 или 4, потому что процессорам Intel до Haswell не хватает исполнительных блоков для обработки всех 4 (слитых) мопов за один цикл. (3 вектора и один слитый add / jl. Две нагрузки сливаются с векторными мопами, частью которых они являются.) Sandybridge и более поздние версии могут выполнять обе загрузки каждый цикл, поэтому выполнима одна итерация за цикл, кроме заголовка цикла .

О, ADDPD имеет задержку в 3 цикла. Таким образом, вам необходимо развернуть и использовать несколько аккумуляторов, чтобы избежать наличия узкой места в цепочке зависимостей, переносимых циклами. Вероятно, разверните на 4, а затем суммируйте 4 аккумулятора в конце. Вам придется делать это в исходном коде даже с встроенными функциями, потому что это изменит порядок операций для математики FP, поэтому компилятор может не захотеть делать это при развертывании.

Таким образом, каждый развернутый циклом 4 будет занимать 4 такта, плюс 1 моп для накладных расходов цикла. На Nehalem, где у вас есть крошечный циклический кэш, но нет кэша UOP, развертывание может означать, что вы должны начать заботиться о пропускной способности декодера. Однако на предварительном песчаном мосту одна нагрузка на часы в любом случае, вероятно, станет узким местом.

Для пропускной способности декодера вы, вероятно, можете использовать ANDPS вместо ANDPD, для кодирования которого требуется на один байт меньше. ИДК, если это поможет.


Расширение этого до 256b ymm регистров потребует AVX2 для наиболее простой реализации (для VPMOVSXBQ ymm). Вы можете ускорить работу только с AVX, сделав два VPMOVSXBQ xmm и комбинируя их с VINSERTF128 или чем-то еще.

2 голосов
/ 01 апреля 2012
...