Есть ли способ имитировать целочисленные побитовые операции для типов _m256 в AVX?

У меня есть логическое выражение, которое мне удалось реализовать в SSE2. Теперь я хотел бы попробовать реализовать это в AVX, используя дополнительный фактор 2 в увеличении параллелизма (от 128-битного типа SIMD до 256). Однако AVX не поддерживает целочисленные операции (что делает AVX2, но я работаю над процессором Sandy Bridge, поэтому в настоящее время это не вариант). Однако, поскольку существуют Встроенные функции AVX для побитовых операций. Я подумал, что могу попробовать, просто преобразовав свои целочисленные типы в типы с плавающей запятой и посмотрев, работает ли это.

Первый тест прошел успешно:

__m256 ones = _mm256_set_ps(1,1,1,1,1,1,1,1);
__m256 twos = _mm256_set_ps(2,2,2,2,2,2,2,2); 
__m256 result = _mm256_and_ps(ones, twos);

Я получаю все 0, как и должен. Одновременно с И вместо этого я получаю результат 2. Но при попытке 11 XOR 4 соответственно:

__m256 elevens = _mm256_set_ps(11,11,11,11,11,11,11,11); 
__m256 fours = _mm256_set_ps(4,4,4,4,4,4,4,4); 
__m256 result2 = _mm256_xor_ps(elevens, fours); 

Результат 6.46e-46 (т.е. близко к 0), а не 15. Одновременное выполнение 11 ИЛИ 4 дает мне значение 22, а не 15, как должно быть. Я не понимаю, почему это так. Это ошибка или какая-то конфигурация, которую я упускаю?

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

У кого-нибудь есть решение этой проблемы, или я должен обновить свой процессор, чтобы включить поддержку AVX2?


person Toby999    schedule 11.12.2013    source источник
comment
Похоже, вы печатаете целое число как число с плавающей запятой, чтобы получить 6.46e-46. Вы уверены, что ваши спецификаторы форматирования printf() верны?   -  person 1''    schedule 11.12.2013
comment
Я не печатал. Я только что проверил значение в отладчике Visual Studio.   -  person Toby999    schedule 11.12.2013


Ответы (2)


Первый тест сработал случайно.

1 в виде числа с плавающей запятой — это 0x3f800000, 2 — это 0x40000000. В общем, так бы не получилось.

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

person harold    schedule 11.12.2013
comment
Ага. Спасибо. Это имеет смысл. Я попробую и отмечу ваш ответ как правильный, если он сработает. - person Toby999; 11.12.2013
comment
@Toby999 Toby999 Но имейте в виду, что на всех современных процессорах Intel версии побитовых логических инструкций с плавающей запятой имеют только 1/3 пропускной способности по сравнению с целочисленными версиями. Поэтому, если вы делаете это для повышения производительности, вы можете подумать дважды. Это может даже иметь неприятные последствия, если вы не ограничены пропускной способностью декодера. - person Mysticial; 11.12.2013
comment
В Sandy и Ivy Bridge целочисленная побитовая логика SSE может передаваться на любой из портов 0, 1 или 5 за один цикл. Это 3 за цикл. Но побитовая логика SSE с плавающей запятой может подключаться к порту 5 только один раз в цикле. Таким образом, он ограничен 1 за цикл. На Haswell то же самое, но с AVX2, что делает этот вопрос спорным. - person Mysticial; 11.12.2013
comment
Вы можете использовать целочисленные операции загрузки и сохранения AVX (например, _mm256_loadu_si256) с AVX, вы просто не можете выполнять целочисленные операции (например, _mm256_add_epi32) с AVX. Таким образом, вы должны иметь возможность загружать целые числа, а затем использовать _mm256_and_ps. - person Z boson; 11.12.2013
comment
Спасибо за дополнительный вклад. Действительно, после успешной реализации начальной версии полного математического выражения версия AVX имеет меньшую пропускную способность, чем версия SSE2. Я думаю, это из-за вашего объяснения Mystical. В любом случае, я не ожидал многого, так как я все равно ближе к максимальной пропускной способности чтения памяти, я думаю. Разочаровывает, однако. ;( - person Toby999; 12.12.2013

Вам не нужен AVX2 для использования целочисленных операций загрузки и сохранения AVX: см. Внутреннее руководство Intel. Таким образом, вы можете загружать свои целые числа с помощью AVX, переинтерпретировать преобразование в число с плавающей запятой, использовать побитовые операции с плавающей запятой, а затем повторно интерпретировать преобразование обратно в целое число. Повторные приведения не генерируют никаких инструкций, они просто радуют компилятор. Попробуй это:

//compiled and ran on an Ivy Bridge system with AVX but without AVX2
#include <stdio.h>
#include <immintrin.h>
int main() {
    int a[8] = {0, 2, 4, 6, 8, 10, 12, 14};
    int b[8] = {1, 1, 1, 1, 1,  1,  1,  1};
    int c[8];

    __m256i a8 = _mm256_loadu_si256((__m256i*)a);
    __m256i b8 = _mm256_loadu_si256((__m256i*)b);
    __m256i c8 = _mm256_castps_si256(
        _mm256_or_ps(_mm256_castsi256_ps(a8), _mm256_castsi256_ps(b8)));
    _mm256_storeu_si256((__m256i*)c, c8);
    for(int i=0; i<8; i++) printf("%d ", c[i]); printf("\n");
    //output: 1 3 5 7 9 11 13 15
}

Конечно, как указал Mystical, возможно, этого и не стоит делать, но это не значит, что вы не можете этого сделать.

person Z boson    schedule 11.12.2013
comment
Спасибо за ваш вклад. Это было полезно, так как поиск правильных внутренних методов занимает много времени. - person Toby999; 12.12.2013
comment
есть варианты для выравнивания переменных, поэтому вам не нужно иметь дело с невыровненными нагрузками - person phuclv; 18.09.2014
comment
@LưuVĩnhPhúc, я работал с предположением, что это больше не имеет значения. Пропускная способность и задержка выровненных и невыровненных инструкций загрузки/сохранения одинаковы для выровненной памяти. Это теория. Но на практике я все еще вижу разницу, поэтому я согласен с вами в том, что следует использовать выровненные инструкции по загрузке. - person Z boson; 18.09.2014