Быстрый способ струбцины целое число в диапазоне 0-255


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

Самый быстрый я смог сделать на моем Атлон II является следующим:

inline
BYTE Clamp(int n)
{
    n &= -(n >= 0);
    return n | ((255 - n) >> 31);
}

Это компилирует вниз в следующей сборке с индекса MSVC 6.0:

setns dl
neg   edx
and   eax, edx
mov   edx, 255
sub   edx, eax
sar   edx, 31
or    dl, al

Есть ли возможность улучшения?



34858
94
задан 3 декабря 2011 в 06:12 Источник Поделиться
Комментарии
8 ответов

Попробовать

 int x=n>255?255:n;
... x<0?0:x ...

Я ожидал этого, чтобы произвести

 mov eax,n
cmp eax,255
cmovgt eax,255 ; conditional mov instruction
test eax,eax
cmovlt eax,0

Если вы используете индекса MSVC шесть, Вы можете не получить условный двигаться инструкция. Попробуйте перейти на современные версии Visual студии.

43
ответ дан 3 декабря 2011 в 07:12 Источник Поделиться

Вот моя попытка:

unsigned char clamp(int n){
int a = 255;
a -= n;
a >>= 31;
a |= n;
n >>= 31;
n = ~n;
n &= a;
return n;
}

Он компилируется в 7 инструкции, которая совпадает с вашей текущей версии. Так он может или не может быть быстрее. Однако я не рассчитал. Но я думаю, что это все один цикл инструкции.

mov eax, 255
sub eax, ecx
sar eax, 31
or al , cl
sar ecx, 31
not cl
and al , cl

27
ответ дан 3 декабря 2011 в 06:12 Источник Поделиться

Мне было бы любопытно, как простой ветвистый решение будет исполнять?

inline char Clamp(int n)
{
if(n < 0)
return 0;
else if(n > 255)
return 255;
return n;
}

7
ответ дан 3 декабря 2011 в 02:12 Источник Поделиться

С помощью GCC/LLVM с на MacOS X и 64-разрядной компиляции и генерации ассемблерного с:

gcc -S -Os clamp.c

где хомут.с содержит:

typedef unsigned char BYTE;

BYTE Clamp_1(int n)
{
n &= -(n >= 0);
return n | ((255 - n) >> 31);
}

BYTE Clamp_2(int n)
{
if (n > 255)
n = 255;
else if (n < 0)
n = 0;
return n;
}

Ассемблер для двух функций (с прологом и эпилогом) является:

    .section        __TEXT,__text,regular,pure_instructions
.globl _Clamp_1
_Clamp_1:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
movl %edi, %eax
shrl $31, %eax
xorl $1, %eax
negl %eax
andl %edi, %eax
movl $255, %ecx
subl %eax, %ecx
sarl $31, %ecx
orl %eax, %ecx
movzbl %cl, %eax
popq %rbp
ret
Leh_func_end1:

.globl _Clamp_2
_Clamp_2:
Leh_func_begin2:
pushq %rbp
Ltmp2:
movq %rsp, %rbp
Ltmp3:
cmpl $256, %edi
jl LBB2_2
movl $255, %edi
jmp LBB2_4
LBB2_2:
testl %edi, %edi
jns LBB2_4
xorl %edi, %edi
LBB2_4:
movzbl %dil, %eax
popq %rbp
ret
Leh_func_end2:

В pushq, popq и рет - это вызов функции накладных расходов. Ваш код (Clamp_1()) собирает до 11 инструкции; мой до 9 (но есть два прыжка в шахту, которая может посеять хаос на конвейерное исполнение). Не подходит к 7 инструкции в вашем оптимизированная версия.

Интересно, однако, когда я использую 4.6.1 ССЗ на один и тот же код, на выходе ассемблер:

    .text
.globl _Clamp_1
_Clamp_1:
LFB0:
movl %edi, %eax
movl $255, %edx
notl %eax
sarl $31, %eax
andl %edi, %eax
subl %eax, %edx
sarl $31, %edx
orl %edx, %eax
ret
LFE0:
.globl _Clamp_2
_Clamp_2:
LFB1:
xorl %edx, %edx
testl %edi, %edi
movl $255, %eax
cmovns %edi, %edx
cmpl $255, %edx
cmovle %edx, %eax
ret
LFE1:

Теперь я вижу 8 инструкция в Clamp_1 и 6 в Clamp_2 только рэт.


Дальнейшие эксперименты показывают, что существует разница в выходе между Оук -ОС -с хомутом.С и ССЗ -с -ОС струбцины.с; бывший генерирует оптимизированные (уменьшенные) выходов, а второй генерирует более подробный вывод.

6
ответ дан 3 декабря 2011 в 07:12 Источник Поделиться

Результат может зависеть немного от того, были ли пиксельные данные предсказуемо чаще в-спектр, чем спектр. Это может быть быстрее, в первом случае:

int clamp(int n)
{
if ((unsigned) n <= 255) {
return n;
}
return (n < 0) ? 0 : 255;
}

3
ответ дан 21 декабря 2013 в 05:12 Источник Поделиться

Используя свой < 0 хомут и изменения > 255, как это складывают?

inline 
BYTE Clamp(int n)
{
n &= -(n >= 0);
return n | ~-!(n & -256);
}

Демонтаж второй линии (ниже) на моей машине есть одна дополнительная инструкция, но нет (дорого) смены.

mov  eax, ecx
and eax, -256
neg eax
sbb eax, eax
or eax, ecx

1
ответ дан 3 декабря 2011 в 07:12 Источник Поделиться

unsigned char clamp(int n) {
return (-(n >= 0) & n) | -(n >= 255);
}

Вы можете оптимизировать это, если можно оптимизировать -(а >= б)

1
ответ дан 4 декабря 2011 в 01:12 Источник Поделиться

что по этому поводу?

x &= 255

Это очень эффективно и не оказывает влияния на значения в нормальном диапазоне. Я хотел бы знать, какая доля ваших результатов нужно зажимать. Если простой и не сделать трюк, попробуйте это:

x &= (~x) >> 8

1
ответ дан 9 августа 2012 в 04:08 Источник Поделиться