Как использовать, если условие в встроенных

Я хочу сравнить две переменные с плавающей запятой, используя встроенные функции. Если сравнение верно, сделай что-нибудь еще, сделай что-нибудь. Я хочу сделать это как нормальное условие if..else. Есть ли способ использовать встроенные функции?

//normal code
vector<float> v1, v2;
for(int i = 0; i < v1.size(); ++i)
if(v1[i]<v2[i])
{
    //do something
}
else
{
    //do something
)

Как это сделать с помощью SSE2 или AVX?

3 ответа

Решение

Я нашел документ, который очень полезен для условных инструкций SIMD. Это идеальное решение моего вопроса. Если... еще условие

Документ: http://saluc.engr.uconn.edu/refs/processors/intel/sse_sse2.pdf

Если вы ожидаете, что v1[i] < v2[i] почти никогда не соответствует действительности, почти всегда соответствует действительности или обычно остается неизменным в течение длительного периода времени (даже если в целом не может быть никакого конкретного смещения), тогда также применяется другой метод, который предлагает "истинную обусловленность" (т.е. не "делать и то и другое, отменить один результат "), цена, конечно, но вы также можете фактически пропустить работу вместо того, чтобы просто игнорировать некоторые результаты.

Этот метод довольно прост, сделайте сравнение (векторизованное), соберите маску сравнения с _mm_movemask_psи тогда у вас есть 3 случая:

  • Все сравнения прошли одинаково, и все они были falseвыполнить соответствующий код "сделать что-то", который теперь, возможно, легче векторизовать, так как условие исчезло.
  • Все сравнения прошли одинаково, и все они были true, так же.
  • Смешанный, используйте более сложную логику. В зависимости от того, что вам нужно, вы можете проверить все биты отдельно (возвращаясь к скалярному коду, но теперь сравнивайте всего 1 FP для всей партии), или использовать один из приемов "итерировать только по (не) установленным битам" (хорошо комбинирует) с помощью битового сканирования для восстановления фактического индекса), или иногда вы можете вернуться к выполнению маскирования и слияния как обычно.

Не все 3 случая всегда актуальны, обычно вы применяете это, потому что предикат почти всегда работает одинаково, делая один из "одинаковых" случаев настолько редким, что вы можете просто смешать его с "смешанным".

Эта техника определенно не всегда полезна. "Смешанный" случай сложный и медленный. Быстрый путь должен быть обычным и достаточно быстрым, чтобы его можно было проверить, можете ли вы его пройти.

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

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

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

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

например, вы можете условно добавить 4 к элементам в аккумуляторе, когда соответствующий элемент соответствует условию с кодом вроде:

__m128i match_counts = _mm_setzero_si128();

for (...) {
    __m128  fvec = something;
    __m128i  condition = _mm_castps_si128( _mm_cmplt_ps(fvec, _mm_setzero_ps()) );  // for elements less than zero
    __m128i masked_constant = _mm_and_si128(condition, _mm_set1_epi32(4));
    match_counts = _mm_add_epi32(match_counts, masked_constant);
}

Очевидно, что это работает хорошо, только если вы можете придумать способ без ветвей, чтобы сделать обе стороны ветви. Инструкция смешивания часто может помочь.

Скорее всего, вы вообще не получите никакого ускорения, если на каждой стороне ветки слишком много работы, особенно если размер вашего элемента составляет 4 байта или больше. (SIMD действительно эффективен, когда вы выполняете 16 операций параллельно с 16 отдельными байтами, и менее эффективен, когда вы выполняете 4 операции с четырьмя 32-битными элементами).

Другие вопросы по тегам