2016-10-05 12 views
2

Я начинаю с SIMD intrinsics, поэтому я благодарю всех за их терпение заранее. У меня есть приложение, включающее сравнение абсолютных различий беззнаковых байтов (я работаю с изображениями в оттенках серого).Как извлечь байты из структуры SSE2 __m128i?

Я попытался AVX, более современные версии SSE и т.д., но в конце концов решил SSE2 кажется достаточным, и имеет большую поддержку отдельных байтов - пожалуйста, поправьте меня, если я ошибаюсь.

У меня есть два вопроса: во-первых, то, что это правильный путь, чтобы загрузить 128-разрядные регистры? Я думаю, что я должен передать данные нагрузки Intrinsics выровненные кратными 128, но будет ли это работать с 2D-кода массива, как это:

greys = aligned_alloc(16, xres * sizeof(int8_t*)); 

for (uint32_t x = 0; x < xres; x++) 
{ 
    greys[x] = aligned_alloc(16, yres * sizeof(int8_t*)); 
} 

(Код выше предполагает XRES и yres одинаковы, и полномочия двух). Это превращается в линейный, непрерывный блок в памяти? Могу ли я тогда, когда я петлю, просто продолжаю передавать адреса (увеличивая их на 128) на встроенные нагрузки SSE2? Или нужно сделать что-то другое для 2D-массивов, подобных этому?

Мой второй вопрос: как только я выполнил всю свою обработку вектора, как я могу извлечь измененные байты из __m128i? Просматривая руководство Intel Intrinsics Guide, инструкции, которые преобразуют векторный тип в скалярный, редки. Самое близкое, что я нашел, это int _mm_movemask_epi8 (__m128i a), но я не совсем понимаю, как его использовать.

Да, и третий вопрос - я предполагал, _mm_load_si128 только загружает подписанные байты? И я не мог найти какую-либо другую функцию загрузки байтов, поэтому, я думаю, вы просто должны вычесть 128 из каждого и позже зачислить ее позже?

Я знаю, что эти основные вопросы для экспертов SIMD, но я надеюсь, что это один будет полезен для начинающих, как я. И если вы считаете, что мой подход к приложению неправильный, или мне будет лучше с более современными расширениями SIMD, я бы с удовольствием узнал. Я просто хотел бы смиренно предупредить, что я никогда не работал с сборкой, и все эти бит-трюки требуют много объяснений, если это поможет мне.

Тем не менее, я благодарен за любые разъяснения имеющихся.

В случае это имеет значение: Я ориентируюсь на маломощные i7 Skylake архитектуры. Но было бы неплохо, чтобы приложение запускалось на гораздо более старых машинах (следовательно, SSE2).

ответ

4

наименее очевидный вопрос первый:

раз я сделал все мои вектор обработки, как, черт возьми, я извлечь измененные байты из __m128i

Extract низкие 64 бита в целое число с int64_t _mm_cvtsi128_si64x(__m128i), или the low 32 bits with int _mm_cvtsi128_si32 (__m128i a). Если вы хотите, чтобы другие части вектора, ваши варианты:

  • перетасовать вектор для создания нового __m128i с данными, который вы хотите в нижнем элементе и использовать вариатор встроенных функций (MOVD или MOVQ в ASM).
  • SSE2 int _mm_extract_epi16 (__m128i a, int imm8) или аналогичные инструкции SSE4.1 для других размеров элементов. PEXTRB/W/D/Q - это не самые быстрые инструкции, но если вам нужен только один высокий элемент, они лучше, чем отдельный Shuffle и MOVD.
  • Хранить во временном массиве или использовать union { __m128i v; int64_t i64[2]; } или что-то в этом роде.Унифицированный тип punning является законным в C99, но только как расширение на C++. Альтернативой союзу, который также будет работать на C++, будет memcpy(&my_int64_local, 8 + (char*)my_vector, 8);, чтобы извлечь верхнюю половину. Надеюсь, это не скомпилируется в хранилище + фактический вызов библиотеки библиотеки memcpy. Компиляторы, как правило, довольно хорошо разбираются в небольшом фиксированном размере memcpy из-за таких случаев использования, но вы, вероятно, получите хранилище/перезагрузку вместо PEXTRQ, даже если вы скомпилируете -msse4.1, чтобы компилятор использовал его, если он хочет , Если результат может перейти непосредственно в память немодифицированного (вместо того, чтобы быть необходимой в целочисленном регистре), смарт-компилятор может использовать MOVHPS хранить высокую половинку __m128i

ли этот поворот в линейный, непрерывный блок в памяти?

Нет, это массив указателей для разделения блоков памяти, представляющий дополнительный уровень косвенности против соответствующего 2D-массива. Не делай этого.

Сделайте одно большое выделение и сделайте расчет индекса самостоятельно (используя array[x*yres + y]).

И да, загрузите данные с него _mm_load_si128 или загрузите, если вам нужно загрузить со смещения.


предполагается _mm_load_si128 только нагрузки подписали байты

Подпись или без знака не является неотъемлемым свойством байта, это только то, как вы интерпретируете биты. Для загрузки двух 64-разрядных элементов или 128-битного растрового изображения используется одна и та же нагрузка.

Используйте свойства, которые подходят для ваших данных. Это немного похоже на язык ассемблера: все просто байты, и машина будет делать то, что вы рассказываете с помощью своих байтов. Вы можете выбрать последовательность инструкций/встроенных функций, которые дают значимые результаты.

Целочисленная нагрузка нагрузки принимает __m128i* указатель args, поэтому вам нужно использовать _mm_load_si128((const __m128i*) my_int_pointer) или аналогичный. Это выглядит как сглаживание указателя (например, чтение массива int через short *), что является неопределенным поведением на C и C++. Тем не менее, именно так Intel заявляет, что вы должны это делать, поэтому любой компилятор, который реализует встроенные функции Intel, должен сделать эту работу правильно. gcc делает это, определяя __m128i с __attribute__((may_alias)).

См. Также Loading data for GCC's vector extensions, в котором указывается, что вы можете использовать встроенные функции Intel для встроенных векторных расширений GNU C и показывает, как загрузить/сохранить.


Чтобы узнать больше о SIMD с SSE, есть некоторые ссылки в тегов вики, включая некоторые интро/учебник ссылки.

тег wiki имеет хорошие ссылки asm/performance x86.

+1

Вы также можете увидеть, будет ли компилятор сначала проинвестизировать его, а затем, если вам нужно больше, что может сделать компилятор, а затем добавьте его позже. в идеале мы все используем код ванили, а компилятор делает SSE, AXV или независимо от того, что поддерживает компьютер, если это то, что создатели компилятора настроили для оптимизации. – Holmz

+0

@Peter Cordez - К сожалению, теперь я не могу даже вычислить способ загрузки моих байтов в 16-байтовый вектор. Команды загрузки/хранения все, кажется, используют векторы как для результатов, так и для аргументов - включая адреса! Моя интуиция заключалась бы в том, что я предоставляю (как аргумент нагрузки) адрес из моего байтового массива, но это невозможно, потому что для наследования нагрузки требуются векторы в качестве аргументов. И я не могу найти встроенный, который возвращает вектор (адрес) из обычного, скалярного 64-битного адреса! – sacheie

+0

@sacheie: справа, для целых чисел вы должны использовать '_mm_loadu_si128 ((const __m128i *) my_int_pointer)'. Вы бы это выяснили, если бы посмотрели на какие-либо учебники, связанные с тегом wikis. –

 Смежные вопросы

  • Нет связанных вопросов^_^