Просто использование обычных int
s для a
, b
и c
позволит вам изменить код на более простой
if (a >= b - c && a <= b + c) ...
Также, в качестве альтернативы, 256 * 256 * 256 - это просто 16 МБ, а карта из 16 Мбит - 2 МБ. Это означает, что возможно использовать справочную таблицу типа
int index = (a<<16) + (b<<8) + c;
if (lookup_table[index>>3] & (1<<(index&7))) ...
но я думаю, что очистка кеша сделает это намного медленнее, даже если современные процессоры ненавидят условные выражения ...
Другая альтернатива - использовать немного алгебры
b - c <= a <= b + c
iff
- c <= a - b <= c (subtracted b from all terms)
iff
0 <= a - b + c <= 2*c (added c to all terms)
это позволяет использовать только один тест
if ((unsigned)(a - b + c) < 2*c) ...
при условии, что a
, b
и c
равны int
с. Причина в том, что если a - b + c
отрицательно, то арифметика без знака сделает его намного больше, чем 2*c
(если c
равно 0..255).
Это должно генерировать эффективный машинный код с одной ветвью, если процессор имеет специальные инструкции сравнения со знаком / без знака, такие как x86 (ja / jg).
#include <stdio.h>
int main()
{
int err = 0;
for (int ia=0; ia<256; ia++)
for (int ib=0; ib<256; ib++)
for (int ic=0; ic<256; ic++)
{
unsigned char a = ia;
unsigned char b = ib;
unsigned char c = ic;
int res1 = (a >= ( b - c > 0 ? b - c : 0 ) &&
a <= ( b + c < 255 ? b + c : 255 ));
int res2 = (unsigned(a - b + c) <= 2*c);
err += (res1 != res2);
}
printf("Errors = %i\n", err);
return 0;
}
На x86 с g ++ код ассемблера, сгенерированный для теста res2
, содержит только одну условную инструкцию.
Код ассемблера для следующего цикла:
void process(unsigned char *src, unsigned char *dst, int sz)
{
for (int i=0; i<sz; i+=3)
{
unsigned char a = src[i];
unsigned char b = src[i+1];
unsigned char c = src[i+2];
dst[i] = (unsigned(a - b + c) <= 2*c);
}
}
.L3:
movzbl 2(%ecx,%eax), %ebx ; This loads c
movzbl (%ecx,%eax), %edx ; This loads a
movzbl 1(%ecx,%eax), %esi ; This loads b
leal (%ebx,%edx), %edx ; This computes a + c
addl %ebx, %ebx ; This is c * 2
subl %esi, %edx ; This is a - b + c
cmpl %ebx, %edx ; Comparison
setbe (%edi,%eax) ; Set 0/1 depending on result
addl $3, %eax ; next group
cmpl %eax, 16(%ebp) ; did we finish ?
jg .L3 ; if not loop back for next
Вместо этого dst[i] = (a<b ? b-a : a-b);
код становится намного длиннее
.L9:
movzbl %dl, %edx
andl $255, %esi
subl %esi, %edx
.L4:
andl $255, %edi
cmpl %edi, %edx
movl 12(%ebp), %edx
setle (%edx,%eax)
addl $3, %eax
cmpl %eax, 16(%ebp)
jle .L6
.L5:
movzbl (%ecx,%eax), %edx
movb %dl, -13(%ebp)
movzbl 1(%ecx,%eax), %esi
movzbl 2(%ecx,%eax), %edi
movl %esi, %ebx
cmpb %bl, %dl
ja .L9
movl %esi, %ebx
movzbl %bl, %edx
movzbl -13(%ebp), %ebx
subl %ebx, %edx
jmp .L4
.p2align 4,,7
.p2align 3
.L6:
И я слишком устал, чтобы пытаться расшифровать его (2:28 утра здесь)
В любом случае, больше не означает, что нужно медленнее (на первый взгляд кажется, что g ++ решил развернуть цикл, написав несколько элементов за раз в этом случае).
Как я уже говорил, вам следует провести некоторое профилирование с вашими реальными вычислениями и вашими реальными данными. Обратите внимание, что если требуется истинная производительность, возможно, лучшая стратегия будет отличаться в зависимости от процессора.
Например, Linux во время начальной загрузки выполняет тест, чтобы определить, какой более быстрый способ выполнить определенные вычисления, необходимые для ядра. Переменных слишком много (размер / уровни кэша, скорость оперативной памяти, тактовая частота процессора, набор микросхем, тип процессора ...).