Как я могу реализовать логическую импликацию с побитовым или другим эффективным кодом в C? - PullRequest
9 голосов
/ 21 марта 2009

Я хочу реализовать логическую операцию, которая работает максимально эффективно. Мне нужна эта таблица истинности:

p    q    p → q
T    T      T
T    F      F
F    T      T
F    F      T

Это, согласно википедии, называется " логическим подтекстом "

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

Спасибо

Ответы [ 5 ]

11 голосов
/ 21 марта 2009
~p | q

Для визуализации:

perl -e'printf "%x\n", (~0x1100 | 0x1010) & 0x1111'
1011

В жестком коде это должно быть быстрее, чем "! P || q", поскольку у последнего есть ветвление, которое может вызвать останов в ЦП из-за ошибки предсказания ветвления. Битовая версия является детерминированной и, в качестве бонуса, может выполнять в 32-битном целом числе в 32 раза больше работы, чем булева версия!

9 голосов
/ 21 марта 2009

К вашему сведению, с gcc-4.3.3:

int foo(int a, int b) { return !a || b; }
int bar(int a, int b) { return ~a | b; }

Дает (из objdump -d):

0000000000000000 <foo>:
   0:   85 ff                   test   %edi,%edi
   2:   0f 94 c2                sete   %dl
   5:   85 f6                   test   %esi,%esi
   7:   0f 95 c0                setne  %al
   a:   09 d0                   or     %edx,%eax
   c:   83 e0 01                and    $0x1,%eax
   f:   c3                      retq   

0000000000000010 <bar>:
  10:   f7 d7                   not    %edi
  12:   09 fe                   or     %edi,%esi
  14:   89 f0                   mov    %esi,%eax
  16:   c3                      retq   

Итак, нет веток, но в два раза больше инструкций.

И даже лучше, с _Bool (спасибо @litb):

_Bool baz(_Bool a, _Bool b) { return !a || b; }
0000000000000020 <baz>:
  20:   40 84 ff                test   %dil,%dil
  23:   b8 01 00 00 00          mov    $0x1,%eax
  28:   0f 45 c6                cmovne %esi,%eax
  2b:   c3                      retq   

Итак, использование _Bool вместо int - хорошая идея.

Поскольку я обновляю сегодня, я подтвердил, что gcc 8.2.0 выдает похожие, но не идентичные результаты для _Bool:

0000000000000020 <baz>:
  20:   83 f7 01                xor    $0x1,%edi
  23:   89 f8                   mov    %edi,%eax
  25:   09 f0                   or     %esi,%eax
  27:   c3                      retq   
9 голосов
/ 21 марта 2009
!p || q

достаточно быстро. серьезно, не беспокойся об этом.

4 голосов
/ 21 марта 2009

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

0 голосов
/ 16 августа 2016

Еще одно решение для C booleans (немного грязно, но работает):

((unsigned int)(p) <= (unsigned int)(q))

Это работает, поскольку по стандарту C 0 представляет false, а любое другое значение true (1 возвращается для true булевыми операторами, int тип).

«Грязность» заключается в том, что я использую логические значения (p и q) в качестве целых чисел, что противоречит некоторым строгим правилам типизации (таким как MISRA), что ж, это вопрос оптимизации. Вы всегда можете #define это как макрос, чтобы скрыть грязные вещи.

Для правильных логических p и q (имеющих двоичные представления 0 или 1) это работает. В противном случае T->T может не дать T, если p и q имеют произвольные ненулевые значения для представления true.

Если вам нужно сохранить только результат, начиная с Pentium II, есть инструкция cmovcc (условное перемещение) (как показано в ответе Дерберта). Для логических значений, однако, даже у 386 была опция без ветвей, инструкция setcc, которая производит 0 или 1 в месте расположения байта результата (регистр байтов или память). Вы также можете видеть это в ответе Дерберта, и это решение также компилируется в результат, включающий setcc (setbe: Устанавливается, если ниже или равно).

Вариант ~p | q Дерберта и Криса Долана должен быть самым быстрым для обработки больших объемов данных, поскольку он может обрабатывать значения для всех битов p и q по отдельности.

Обратите внимание, что даже решение !p || q не компилируется с переходным кодом на x86: оно использует инструкции setcc. Это лучшее решение, если p или q могут содержать произвольные ненулевые значения, представляющие значение true. Если вы используете тип _Bool, он сгенерирует очень мало инструкций.

При компиляции для x86 я получил следующие цифры:

__attribute__((fastcall)) int imp1(int a, int b)
{
 return ((unsigned int)(a) <= (unsigned int)(b));
}

__attribute__((fastcall)) int imp2(int a, int b)
{
 return (!a || b);
}

__attribute__((fastcall)) _Bool imp3(_Bool a, _Bool b)
{
 return (!a || b);
}

__attribute__((fastcall)) int imp4(int a, int b)
{
 return (~a | b);
}

Результат сборки:

00000000 <imp1>:
   0:   31 c0                   xor    %eax,%eax
   2:   39 d1                   cmp    %edx,%ecx
   4:   0f 96 c0                setbe  %al
   7:   c3                      ret    

00000010 <imp2>:
  10:   85 d2                   test   %edx,%edx
  12:   0f 95 c0                setne  %al
  15:   85 c9                   test   %ecx,%ecx
  17:   0f 94 c2                sete   %dl
  1a:   09 d0                   or     %edx,%eax
  1c:   0f b6 c0                movzbl %al,%eax
  1f:   c3                      ret    

00000020 <imp3>:
  20:   89 c8                   mov    %ecx,%eax
  22:   83 f0 01                xor    $0x1,%eax
  25:   09 d0                   or     %edx,%eax
  27:   c3                      ret    

00000030 <imp4>:
  30:   89 d0                   mov    %edx,%eax
  32:   f7 d1                   not    %ecx
  34:   09 c8                   or     %ecx,%eax
  36:   c3                      ret    

При использовании типа _Bool компилятор явно использует, что у него есть только два возможных значения (0 для false и 1 для true), что дает результат, очень похожий на решение ~a | b (единственное разница в том, что последний выполняет дополнение для всех битов, а не только младший бит).

Компиляция для 64 бит дает примерно такие же результаты.

В любом случае, ясно, что метод не имеет значения с точки зрения избежания создания условных выражений.

...