SIMD / SSE: краткое точечное произведение и краткое максимальное значение

Я пытаюсь оптимизировать точечный продукт двух массивов в стиле c: contant и small size и типа short.

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

Однако я не понимаю, как точечный продукт на коротких массивах с использованием этой встроенной функции может дать правильный результат. При создании скалярного произведения вычисленные значения могут быть (и всегда в моем случае) больше, чем SHORT_MAX, поэтому есть сумма. Следовательно, я храню их в переменной типа double.

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

спасибо за советы


person florian    schedule 09.02.2015    source источник
comment
Почему вы хотите оптимизировать скалярное произведение двух маленьких массивов? Что еще вы делаете с этими массивами? Что вы делаете в своем критическом цикле? Некоторые примеры кода могут помочь.   -  person Z boson    schedule 10.02.2015
comment
Я пытаюсь оптимизировать скалярное произведение, поскольку это занимает 80% времени всего процесса: почти 15 миллионов различных скалярных произведений за один прогон, так что любое улучшение может иметь огромный эффект.   -  person florian    schedule 10.02.2015


Ответы (2)


В зависимости от диапазона значений ваших данных вы можете использовать встроенную функцию, например _ mm_madd_epi16, который выполняет умножение / сложение 16-битных данных и генерирует 32-битные члены. Затем вам нужно будет периодически накапливать 32-битные термины до 64-битных. Как часто вам нужно это делать, зависит от диапазона ваших входных данных, например если это 12-битные данные изображения в градациях серого, вы можете выполнить 64 итерации с 8 элементами на итерацию (т.е. 512 входных точек), прежде чем возникнет вероятность переполнения. Однако в худшем случае, если ваши входные данные используют полный 16-битный диапазон, вам нужно будет делать дополнительные 64-битные накопления на каждой итерации (т.е. каждые 8 ​​точек).

person Paul R    schedule 09.02.2015
comment
Я не видел этой функции, а теперь вижу свет. Я реализовал точечный продукт, и он работает, несмотря на переполнение. У меня прибавка в скорости почти на 25%, что для меня огромно! - person florian; 10.02.2015
comment
_mm_madd_epi16 находится где-то в верхней части моего списка 10 лучших встроенных функций SSE за все время. ;-) Помимо точечных произведений, он также полезен для сокращения типов статистики, например sum (x), sum (x ^ 2) и т. д. Кстати, если вы хотите опубликовать свой код в качестве нового вопроса, тогда вы сможете улучшить свой 25% прирост. - person Paul R; 10.02.2015
comment
@florian, я забыл про _mm_madd_epi16. Спасибо Полу за то, что снова напомнил мне об этом. Было бы интересно увидеть ваш код и многое другое из того, что вы делаете, как предложил Пол. Ваша операция, вероятно, ограничена пропускной способностью памяти, поэтому вы мало что можете сделать, кроме как изменить то, что вы делаете, но трудно сказать наверняка, не зная больше о том, что вы делаете. - person Z boson; 11.02.2015
comment
Да, я тоже предполагал, что эти операции ограничены пропускной способностью памяти. Кроме того, общий процесс распараллелен, так что я не ожидаю прироста на 25%, но меньше этого. Но это уже огромное улучшение (я ожидал 2/5%). Я выложу свой код позже, а зачем в другой теме? - person florian; 11.02.2015
comment
и, как еще один комментарий, соответствующий код avx работает медленнее. - person florian; 11.02.2015

Просто для записей, вот как я делаю скалярное произведение для двух массивов int16 размером 36:

double dotprod(const int16_t* source, const int16_t* target, const int size){
#ifdef USE_SSE
    int res[4];
    __m128i* src = (__m128i *) source;
    __m128i* t = (__m128i *) target;
    __m128i s = _mm_madd_epi16(_mm_loadu_si128(src), mm_loadu_si128(t));
    ++src;
    ++t;
    s = _mm_add_epi32(s, _mm_madd_epi16(_mm_loadu_si128(src), _mm_loadu_si128(t)));
    ++src;
    ++t;
    s = _mm_add_epi32(s, _mm_madd_epi16(_mm_loadu_si128(src), _mm_loadu_si128(t)));
    ++src;
    ++t;
    s = _mm_add_epi32(s, _mm_madd_epi16(_mm_loadu_si128(src), _mm_loadu_si128(t)));

    /* return the sum of the four 32-bit sub sums */
    _mm_storeu_si128((__m128i*)&res, s);
    return res[0] + res[1] + res[2] + res[3] + source[32] * target[32] + source[33] * target[33] + source[34] * target[34] + source[35] * target[35];
#elif USE_AVX
    int res[8];
    __m256i* src = (__m256i *) source;
    __m256i* t = (__m256i *) target;
    __m256i s = _mm256_madd_epi16(_mm256_loadu_si256(src), _mm256_loadu_si256(t));
    ++src;
    ++t;
    s = _mm256_add_epi32(s, _mm256_madd_epi16(_mm256_loadu_si256(src), _mm256_loadu_si256(t)));

    /* return the sum of the 8 32-bit sub sums */
    _mm256_storeu_si256((__m256i*)&res, s);
    return res[0] + res[1] + res[2] + res[3] + res[4] + res[5] + res[6] + res[7] + source[32] * target[32] + source[33] * target[33] + source[34] * target[34] + source[35] * target[35];
#else
    return source[0] * target[0] + source[1] * target[1] + source[2] * target[2] + source[3] * target[3] + source[4] * target[4]+ source[5] * target[5] + source[6] * target[6] + source[7] * target[7] + source[8] * target[8] + source[9] * target[9] + source[10] * target[10] + source[11] * target[11] + source[12] * target[12] + source[13] * target[13] + source[14] * target[14] + source[15] * target[15] + source[16] * target[16] + source[17] * target[17] + source[18] * target[18] + source[19] * target[19] + source[20] * target[20] + source[21] * target[21] + source[22] * target[22] + source[23] * target[23] + source[24] * target[24] + source[25] * target[25] + source[26] * target[26] + source[27] * target[27] + source[28] * target[28] + source[29] * target[29] + source[30] * target[30] + source[31] * target[31] + source[32] * target[32] + source[33] * target[33] + source[34] * target[34] + source[35] * target[35];
#endif
}
person florian    schedule 16.02.2015