Безветровый код, который отображает нулевые, отрицательные и положительные значения в 0, 1, 2

Записывайте ветвящуюся функцию, которая возвращает 0, 1 или 2, если разница между двумя целыми числами равна нулю, отрицательна или положительна.

Здесь версия с ветвлением:

int Compare(int x, int y)
{
    int diff = x - y;
    if (diff == 0)
        return 0;
    else if (diff < 0)
        return 1;
    else
        return 2;
}

Здесь версия, которая может быть быстрее в зависимости от компилятора и процессора:

int Compare(int x, int y)
{
    int diff = x - y;
    return diff == 0 ? 0 : (diff < 0 ? 1 : 2);
}

Можете ли вы придумать более быстрый вариант без ветвей?

СУЩНОСТЬ

10 решений, которые я тестировал, имели аналогичную производительность. Фактические числа и победитель варьировались в зависимости от компилятора (icc/gcc), параметров компилятора (например, -O3, -march = nocona, -fast, -xHost) и машины. Решение Canon хорошо зарекомендовало себя во многих тестах, но опять же преимущество в производительности было незначительным. Я был удивлен, что в некоторых случаях некоторые решения были медленнее, чем наивное решение с ветвями.

Ответ 1

int Compare(int x, int y) {
     return (x < y) + (y < x) << 1;
}

Изменить: только побитовое? Guess < и > не считайте, то?

int Compare(int x, int y) {
    int diff = x - y;
    return (!!diff) | (!!(diff & 0x80000000) << 1);
}

Но вот что надоедает -.

Изменить: сдвиньте другую сторону.

Мех, просто повторите попытку:

int Compare(int x, int y) {
    int diff = y - x;
    return (!!diff) << ((diff >> 31) & 1);
}

Но я предполагаю, что нет стандартной инструкции ASM для !!. Кроме того, << можно заменить на +, в зависимости от того, что быстрее...

Бит-сплетение - это весело!

Хм, я только что узнал о setnz.

Я не проверял выход ассемблера (но на этот раз я немного его протестировал), и с некоторой удачей он мог бы сохранить целую инструкцию!

В ТЕОРИИ. МОЙ СБОРКА - RUSTY

subl  %edi, %esi
setnz %eax
sarl  $31, %esi
andl  $1, %esi
sarl  %eax, %esi
mov   %esi, %eax
ret

Рамблинг - это весело.

Мне нужен сон.

Ответ 2

Нерентабельный (на уровне языка) код, который отображает отрицательные значения на -1, от нуля до 0 и положителен на +1, выглядит следующим образом

int c = (n > 0) - (n < 0);

если вам нужно другое сопоставление, вы можете просто использовать явную карту для его переназначения

const int MAP[] = { 1, 0, 2 };
int c = MAP[(n > 0) - (n < 0) + 1];

или, для запрошенного сопоставления, используйте некоторый численный трюк, например

int c = 2 * (n > 0) + (n < 0);

(Очевидно, очень легко сгенерировать любое отображение из этого, пока 0 отображается на 0. И код вполне читабельен. Если 0 сопоставляется с чем-то другим, он становится более сложным и менее читаемым.)

В качестве дополнительной заметки: сравнение двух целых чисел путем вычитания одного из другого на уровне языка C является ошибочным методом, поскольку он обычно подвержен переполнению. Красота вышеупомянутых методов заключается в том, что они могут быть немедленно использованы для "вычитаемых" сравнений, таких как

int c = 2 * (x > y) + (x < y);

Ответ 3

Предполагая дополнение 2s, арифметический сдвиг вправо и отсутствие переполнения в вычитании:

#define SHIFT (CHARBIT*sizeof(int) - 1)

int Compare(int x, int y)
{
    int diff = x - y;
    return -(diff >> SHIFT) - (((-diff) >> SHIFT) << 1);
}

Ответ 4

Два дополнения:

#include <limits.h>
#define INT_BITS (CHAR_BITS * sizeof (int))

int Compare(int x, int y) {
    int d = y - x;
    int p = (d + INT_MAX) >> (INT_BITS - 1);
    d = d >> (INT_BITS - 2);
    return (d & 2) + (p & 1);
}

Если предположить компилятор, который не будет использоваться, это не вызовет аппаратное обеспечение сравнения вашей системы и не использует сравнение на языке. Чтобы проверить: если x == y, то d и p будут явно равны 0, поэтому конечный результат будет равен нулю. Если (x - y) > 0, то ((x - y) + INT_MAX) установит высокий бит целого числа, иначе он будет отменен. Таким образом, p будет иметь свой младший бит, если и только если (x - y) > 0. Если (x - y) < 0, то его старший бит будет установлен, а d будет устанавливать свой бит с младшим разрядом.

Ответ 5

Беззнаковое сравнение, которое возвращает -1,0,1 (cmpu), является одним из случаев, на которое проверяется GNU SuperOptimizer.

cmpu: compare (unsigned)
int cmpu(unsigned_word v0, unsigned_word v1)
{
    return ( (v0 > v1) ? 1 : ( (v0 < v1) ? -1 : 0) );
}

SuperOptimizer исчерпывающе просматривает пространство команд для наилучшей комбинации инструкций, которые будут реализовывать данную функцию. Предполагается, что компиляторы автоматически заменяют вышеперечисленные функции своими супероптимизированными версиями (хотя не все компиляторы сделай это). Например, в PowerPC Compiler Writer Guide (powerpc-cwg.pdf), функция cmpu показана в Приложении D стр. 204:

cmpu: compare (unsigned)
PowerPC SuperOptimized Version
subf  R5,R4,R3
subfc R6,R3,R4
subfe R7,R4,R3
subfe R8,R7,R5

Это очень хорошо, не так ли... всего четыре вычитания (и с переносом и/или расширенными версиями). Не говоря уже о том, что действительно не имеет ветки на уровне машинного кода. Вероятно, эквивалентна последовательность PC/Intel X86, которая аналогична короткой, так как GNU Superoptimizer работает для X86, а также для PowerPC.

Обратите внимание, что Unsigned Comparison (cmpu) можно преобразовать в Signed Comparison (cmps) в 32-разрядное сравнение, добавив 0x80000000 для обоих входов Signed, прежде чем передавать его в cmpu.

cmps: compare (signed)
int cmps(signed_word v0, signed_word v1)
{
    signed_word offset=0x80000000;
    return ( (unsigned_word) (v0 + signed_word),
        (unsigned_word) (v1 + signed_word) );
}

Это всего лишь один вариант, хотя... SuperOptimizer может найти cmps, который короче и не требует добавления смещений и вызова cmpu.

Чтобы получить запрошенную версию, которая возвращает ваши значения {1,0,2}, а не {-1,0,1}, используйте следующий код, который использует функцию SuperOptimized cmps.

int Compare(int x, int y)
{
    static const int retvals[]={1,0,2};
    return (retvals[cmps(x,y)+1]);
}

Ответ 6

Я соглашаюсь с исходным ответом Тордека:

int compare(int x, int y) {
    return (x < y) + 2*(y < x);
}

Компиляция с gcc -O3 -march=pentium4 приводит к отсутствию кода без ветвей, который использует условные инструкции setg и setl (см. объяснение инструкций x86).

push   %ebp
mov    %esp,%ebp
mov    %eax,%ecx
xor    %eax,%eax
cmp    %edx,%ecx
setg   %al
add    %eax,%eax
cmp    %edx,%ecx
setl   %dl
movzbl %dl,%edx
add    %edx,%eax
pop    %ebp
ret 

Ответ 7

Добрый бог, это преследовало меня.

Как бы то ни было, я думаю, что я вытеснил последнюю каплю производительности:

int compare(int a, int b) {
    return (a != b) << (a > b);
}

Хотя компиляция с -O3 в GCC даст (медведь со мной, я делаю это из памяти)

xorl  %eax, %eax
cmpl  %esi, %edi
setne %al
cmpl  %esi, %edi
setgt %dl
sall  %dl, %eax
ret

Но второе сравнение кажется (в соответствии с небольшим количеством тестов, я сосать в ASM), чтобы быть избыточным, оставив маленький и красивый

xorl  %eax, %eax
cmpl  %esi, %edi
setne %al
setgt %dl
sall  %dl, %eax
ret

(Sall может полностью не быть инструкцией ASM, но я точно не помню)

Итак... если вы еще не возражаете, чтобы запустить свой тест еще раз, мне бы хотелось услышать результаты (мой дал 3% -ное улучшение, но это может быть неправильно).

Ответ 8

Объединяя ответы Стивена Канона и Тордека:

int Compare(int x, int y)
{
    int diff = x - y; 
    return -(diff >> 31) + (2 & (-diff >> 30));
} 

Выход: (g++ -O3)

subl     %esi,%edi 
movl     %edi,%eax
sarl     $31,%edi
negl     %eax
sarl     $30,%eax
andl     $2,%eax
subl     %edi,%eax 
ret 

Tight! Однако версия Paul Ssieh содержит еще меньше инструкций:

subl     %esi,%edi
leal     0x7fffffff(%rdi),%eax
sarl     $30,%edi
andl     $2,%edi
shrl     $31,%eax
leal     (%rdi,%rax,1),%eax
ret

Ответ 9

int Compare(int x, int y)
{
    int diff = x - y;

    int absdiff = 0x7fffffff & diff; // diff with sign bit 0
    int absdiff_not_zero = (int) (0 != udiff);

    return
        (absdiff_not_zero << 1)      // 2 iff abs(diff) > 0
        -
        ((0x80000000 & diff) >> 31); // 1 iff diff < 0
}

Ответ 10

Для 32 значащих целых чисел (например, на Java) попробуйте:

return 2 - ((((x >> 30) & 2) + (((x-1) >> 30) & 2))) >> 1;

где (x >> 30) & 2 возвращает 2 для отрицательных чисел и 0 в противном случае.

x было бы разницей двух целых чисел ввода