Как я могу реализовать логическую импликацию с помощью побитового или другого эффективного кода в C?
Я хочу реализовать логическую операцию, которая работает максимально эффективно. Мне нужна эта таблица правды:
p q p → q
T T T
T F F
F T T
F F T
это, согласно Википедии, называется "логическая импликация"
Я давно пытаюсь понять, как сделать это с побитовыми операциями в c/" class="blnk">C без использования условных обозначений. Может кто-то имеет мысли по этому поводу.
спасибо
5 ответов
FYI, с 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 x1,%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 x1,%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 x1,%edi
23: 89 f8 mov %edi,%eax
25: 09 f0 or %esi,%eax
27: c3 retq
~p | q
для наглядности:
perl -e'printf "%x\n", (~0x1100 | 0x1010) & 0x1111'
1011
в плотном коде это должно быть быстрее, чем "!p / / q", потому что у последнего есть ветвь, которая может вызвать сбой в ЦП из-за ошибки прогнозирования ветви. Побитовая версия детерминирована и, в качестве бонуса, может выполнять в 32 раза больше работы в 32-разрядном целочисленном, чем булева версия!
вы можете прочитать о получение логических выражений из таблиц истинности (см. каноническая форма), о том, как вы можете выразить любую таблицу истинности как комбинацию булевых примитивов или функций.
другое решение для булевых c (немного грязное, но работает):
((unsigned int)(p) <= (unsigned int)(q))
он работает с стандарта C,0
- значение false, а любое другое значение true (1
возвращается для true логическими операторами,int
type).
"грязность" заключается в том, что я использую booleans (p
и q
) как целые числа, что противоречит некоторым сильным политикам ввода (например, MISRA), ну, это вопрос оптимизации. Вы всегда можете #define
это как макрос, чтобы скрыть грязные вещи.
для правильного boolean p
и q
(либо 0
или 1
двоичные представления) он работает. В противном случае T->T
может не произвести T
если p
и q
имеют произвольные ненулевые значения для представления true.
Если вам нужно сохранить результат только, так как Pentium II, есть cmovcc
(условного перехода) инструкция (как показано в ответ по Derobert). Для булевых, однако, даже 386 вариант без ветвей,setcc
инструкция, которая производит 0
или 1
в месте байта результата (регистр или память байта). Вы также можете увидеть, что в ответ Derobert, и это решение также компилируется в результате с участием setcc
(setbe
: установить, если ниже или равна).
Derobert и Крис Долан ~p | q
вариант должен быть самым быстрым для обработки больших объемов данных, так как он может обрабатывать импликацию на всех битах p
и q
индивидуально.
обратите внимание, что даже !p || q
решение компилируется в разветвленный код на x86: он использует setcc
инструкция. Это лучшее решение, если p
или q
может содержать произвольные ненулевые значения, представляющие правда. Если вы используете _Bool
type, он будет генерировать очень мало инструкций.
при компиляции для 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 x1,%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 бит дает примерно те же результаты.
в любом случае, ясно, что метод на самом деле не имеет значения с точки зрения избежания создания условий.