Насыщенный короткий (int16) в C++

Я оптимизирую код узкого места:

int sum = ........
sum = (sum >> _bitShift);

if (sum > 32000)
    sum = 32000; //if we get an overflow, saturate output
else if (sum < -32000)
    sum = -32000; //if we get an underflow, saturate output

short result = static_cast<short>(sum);

Я хотел бы написать условие насыщения как одно условие "if" или даже лучше без условия "if", чтобы сделать этот код быстрее. Я не нуждаюсь в насыщении точно в значении 32000, любое подобное значение как 32768 приемлемо.

Согласно этой странице, в ARM есть инструкция по насыщению. Есть ли что-нибудь подобное в x86/x64?

2 ответа

Решение

Вы уверены, что можете победить компилятор в этом?

Вот x64 retail с включенной оптимизацией максимального размера. Visual Studio v15.7.5.

ecx содержит начальное значение в начале этого блока. Eax заполняется насыщенным значением, когда это сделано.

    return (x > 32767) ? 32767 : ((x < -32768) ? -32768 : x);
mov         edx,0FFFF8000h  
movzx       eax,cx  
cmp         ecx,edx  
cmovl       eax,edx  
mov         edx,7FFFh  
cmp         ecx,edx  
movzx       eax,ax  
cmovg       eax,edx 

Я совсем не уверен, что попытка устранить if заявление (я), вероятно, принесет какую-то реальную пользу. Быстрая проверка показывает, что с учетом этого кода:

int clamp(int x) {
    if (x < -32768)
        x = -32768;
    else if (x > 32767)
        x = 32767;
    return x;
}

... и gcc, и Clang дают такие результаты без веток:

clamp(int):
  cmp edi, 32767
  mov eax, 32767
  cmovg edi, eax
  mov eax, -32768
  cmp edi, -32768
  cmovge eax, edi
  ret

Вы можете сделать что-то вроде x = std::min(std::max(x, -32768), 32767);, но при этом получается та же последовательность, и источник кажется менее читаемым, по крайней мере, мне.

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

С чем-то вроде AVX вы, как правило, захотите использовать такую ​​инструкцию, как _mm256_adds_epi16 взять вектор из 16 значений (по 16 бит на штуку) и выполнить насыщающее сложение для всех них одновременно (или, аналогично, _mm256_subs_epi16 делать насыщающее вычитание).

Поскольку вы пишете на C++, я привел выше имена встроенных функций компилятора, используемых в большинстве современных компиляторов (gcc, icc, clang, msvc) для процессоров x86. Если вы пишете на ассемблере напрямую, инструкциями будут vpaddsw и vpsubsw соответственно.

Если вы можете рассчитывать на действительно текущий процессор (который поддерживает инструкции AVX 512), вы можете использовать их вместо этого для одновременной работы с вектором из 32 16-битных значений.

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