2012-12-25 14 views
13

Rozważmy następujący:Mnożenie logiczne w C++?

inline unsigned int f1(const unsigned int i, const bool b) {return b ? i : 0;} 
inline unsigned int f2(const unsigned int i, const bool b) {return b*i;} 

Składnia f2 jest bardziej zwarta, ale nie gwarantuje, że standardowe f1 i f2 są ściśle równoważne?

Co więcej, jeśli chcę, aby kompilator optymalizował to wyrażenie, jeśli b i i są znane podczas kompilacji, którą wersję powinienem wybrać?

+2

Nie można wiedzieć, co "kompilator" zrobi z wyrażeniem (w przypadku argumentów znanych w czasie kompilacji) bez określenia, który kompilator, ale prawdopodobnie zoptymalizuje oba. Jeden ze sposobów opowiedzenia, skompiluj każdy ze znanych w czasie kompilacji argumentów i sprawdź wynik. –

+1

Spójrz na wygenerowany kod zespołu – James

+1

FWIW, MSVC generuje maskę i dodaje do pierwszego przypadku, a mnoży na sekundę, w przypadku, gdy uniemożliwiłem mu wykonanie sztuczek z oceną samego boolowskiego (jeśli to możliwe, zmieni test, który generuje bool w celu dalszej optymalizacji). Zasadniczo MSVC jest w obu przypadkach bez gałęzi i wydaje się bardziej optymalny w przypadku naiwnym. – JasonD

Odpowiedz

11

Cóż, oba są równoważne. bool jest typem integralnym, a true ma gwarantowaną konwersję na 1 w kontekście całkowitym, natomiast false gwarantuje konwersję na 0.

(Odwrotna jest również prawdą, czyli niezerowe wartości całkowite są gwarantowane do konwersji true w kontekście logicznym, przy zerowej wartości całkowite są gwarantowane do konwersji false w kontekście logicznym.)

Ponieważ pracujemy z niepodpisanych typów, można łatwo wymyślić inne, być może nieco-Hack oparte jeszcze doskonale przenośne implementacje tego samego, jak

i & -(unsigned) b 

chociaż przyzwoity kompilator powinien móc wybrać najlepszą realizację sama dla dowolna z twoich wersji.

P.S. Chociaż ku mojemu wielkiemu zaskoczeniu, GCC 4.1.2 skompilował wszystkie trzy warianty dosłownie, tj. Użył instrukcji mnożenia maszynowego w wariancie opartym na multiplikacji. Był wystarczająco inteligentny, aby użyć instrukcji cmovne w wariancie ?:, aby uczynić ją bez gałęzi, co prawdopodobnie uczyniło ją najbardziej wydajną implementacją.

1

Kompilator użyje niejawnej konwersji, aby wykonać unsigned int z b, więc tak, to powinno działać. Pomijasz sprawdzanie warunków przez proste mnożenie. Który z nich jest skuteczniejszy/szybszy? Nie wiem. Dobry kompilator najprawdopodobniej zoptymalizuje obie wersje, które zakładam.

5

Tak. Jest bezpiecznie założyć true jest 1 i false jest 0 gdy używane w wyrażeniach, jak to zrobić i jest gwarantowana:

C++ 11, integralny Promocje, 4,5:

rvalue typu bool możliwe zostać przekonwertowane na wartość typu int, z false staje się zerem, a true staje się jednym.

0

FWIW poniższy kod

inline unsigned int f1(const unsigned int i, const bool b) {return b ? i : 0;} 
inline unsigned int f2(const unsigned int i, const bool b) {return b*i;} 

int main() 
{ 
    volatile unsigned int i = f1(42, true); 
    volatile unsigned int j = f2(42, true); 
} 

skompilowane z gcc -O2 produkuje ten montaż:

.file "test.cpp" 
    .def ___main; .scl 2; .type 32; .endef 
    .section .text.startup,"x" 
    .p2align 2,,3 
    .globl _main 
    .def _main; .scl 2; .type 32; .endef 
_main: 
LFB2: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $16, %esp 
    call ___main 
    movl $42, 8(%esp) // i 
    movl $42, 12(%esp) // j 
    xorl %eax, %eax 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
LFE2: 

nie ma wiele w lewo z obu f1 lub f2, jak widać.

Jeśli chodzi o standard C++, kompilator może robić wszystko, co dotyczy optymalizacji, o ile nie zmienia obserwowalnego zachowania (reguła , tak jakby była).

+2

Aby uzyskać reprezentatywny wynik, prawdopodobnie powinieneś używać argumentów nieprzewidywalnych (przez kompilator) do funkcji 'f1 (rand(), rand())' byłoby lepszym przykładem. – AnT