Interested in the question that is optimal for bool operation:

if (var1) var2 = false else var2 = true 

or

 var2 = !var1 

In other words, is it better to compare and subject to the desired value, or is it easier to assign a negative right away?

  • 3
    Of course the second option. The first is not only slow, but also confusing. - Uraty
  • 7
    Do you really care about one nanosecond optimization? Wanghui that the optimizing compiler will generate exactly the same code. - VladD
  • 2
    If in any compiler the first variant is more optimal than the second one - this is a good reason to think about the quality of the execution of this compiler. - αλεχολυτ
  • one
    @VladD So it is - see my answer, especially for Open Watcom :) - Harry

5 answers 5

I am writing for a microprocessor - this is the idea that the second option is much faster, I just do not know the assembler.

It all depends on the compiler. It can inline this function to the place of the call and do anything with it, depending on the surrounding code. In general, I would expect the same code to come out, although this may not be the case.

It may be recalled that in a C, any non-zero value is true, therefore, inverting itself simply does not do that. As I understand it, it should decompose into a test statement and a conditional branch.

Options for how I could write this in assembly language (approximately, at the level of ideas). I don't specifically use ret, assuming that the function will be inline.

  1. The most primitive variant, is similar to the first code without any optimizations:

     test a jz ZERO jmp NONZERO ZERO: mov b 1 jmp DONE NONZERO: mov b 0 DONE: no 

    Check, conditional jump, assignment, unconditional jump (not always).

  2. Looks like the first one, a bit optimized:

     test a jz ZERO mov b 0 jmp DONE ZERO: mov b 1 DONE: nop 

    Check, assignment, exactly 1 transition (but in one of two places).

  3. b = 0; if (!a) b = 1; optimized:

     xor b, b // то же, что `mov b 0`, но быстрее test a jnz NONZERO mov b 1 NONZERO: nop 

    For true a : Assignment, validation, transition.
    For false a : Assignment, check, assignment.

I think for the option b = !a compiler will choose the best option. This is a very simple construction, with which it should be easier for him to understand, so you should choose it. Anyway, it is readable.

  1. Yes, and another trick. If the compiler is sure that there is 0 or 1, it can replace the invert with xor like this b = a ^ 1 :

     mov ba xor b 1 

    One move and one bit operation.

PS: And he can use specific commands that I do not know or do not remember. Or which is only in a specific processor.

  • one
    Option 4 is good. X86 has a sete command (found out thanks to this question and gcc -S) - "Sets the operand to 0." / gcc -O3 uses just her after testl - avp
  • Generally without transitions :) mov ebx, [var2] // xor eax, eax // sub ebx, 1 // adc eax, 0 // mov [var1], eax However, it’s a bit long and useless considering other options. - PinkTux
  • I thought about option 4 when I asked a question. But judging by the comments, it turns out that this operation is processed by virtue of the compiler's capabilities and, as a rule, the result is either the same number of steps, or more optimally in the second case. Well, thanks for the comments) - FoeNicks

@Uraty nice painted, I will try to go on the other side.

Wrote two small methods.

 private static boolean m1(boolean value) { boolean result; if (value) result = false; else result = true; return result; } private static boolean m2(boolean value) { boolean result = !value; return result; } 

Compiled and looked at baytkod:

 private static boolean m1(boolean); 0: iload_0 1: ifeq 9 4: iconst_0 5: istore_1 6: goto 11 9: iconst_1 10: istore_1 11: iload_1 12: ireturn private static boolean m2(boolean); 0: iload_0 1: ifne 4: iconst_1 5: goto 9 8: iconst_0 9: istore_1 10: iload_1 11: ireturn 

It’s not necessary to understand everything, it’s worth noting that both methods produced almost the same number of instructions, and they are almost identical, except for one instruction: in the m1() method there is ifeq in m2() ifen is a test for equality and a test for inequality.

It can be concluded that they are identical, but a record of the form:

 boolean result = !value 

looks more concise.

  • "in both methods the same number of instructions was obtained" - uh ... is there more in the second one? 8 vs 9. - Qwertiy
  • ah, yes, you are right) - Artem Konovalov
  • Is it a byte code? that is, did it simply negate the usual Boolean variable into an if-goto? - KoVadim
  • @KoVadim aha, that’s it - Artem Konovalov

C ++ is optimized in the same way :

 bool f(bool var1) { bool var2 = !var1; return var2; } bool g(bool var1) { bool var2; if (var1) var2 = false; else var2 = true; return var2; } 

turn into the same thing:

 f(bool): mov eax, edi xor eax, 1 ret g(bool): mov eax, edi xor eax, 1 ret 

Update Yes, it is indeed C ++, I repent. Well, let's take VC ++ 2015. In C ++ mode, the code is the same with optimization:

 g: cmp BYTE PTR _var1$[esp-4], 0 sete al f: cmp BYTE PTR _var1$[esp-4], 0 sete al 

Without optimization, everything is scary;)

 g: push ebp mov ebp, esp push ecx movzx eax, BYTE PTR _var1$[ebp] test eax, eax je SHORT $LN2@g mov BYTE PTR _var2$[ebp], 0 jmp SHORT $LN3@g $LN2@g: mov BYTE PTR _var2$[ebp], 1 $LN3@g: mov al, BYTE PTR _var2$[ebp] mov esp, ebp pop ebp ret 0 f: push ebp mov ebp, esp sub esp, 8 movzx eax, BYTE PTR _var1$[ebp] test eax, eax jne SHORT $LN3@f mov DWORD PTR tv66[ebp], 1 jmp SHORT $LN4@f $LN3@f: mov DWORD PTR tv66[ebp], 0 $LN4@f: mov cl, BYTE PTR tv66[ebp] mov BYTE PTR _var2$[ebp], cl mov al, BYTE PTR _var2$[ebp] mov esp, ebp pop ebp ret 0 

Those. long, but almost the same.

For C code slightly corrected:

 #define bool _Bool bool f(bool var1) { bool var2 = !var1; return var2; } bool g(bool var1) { bool var2; if (var1) var2 = 0; else var2 = 1; return var2; } 

We get with optimization exactly the same as for C ++ :

 _g PROC ; COMDAT cmp BYTE PTR _var1$[esp-4], 0 sete al ret 0 _g ENDP _f PROC ; COMDAT cmp BYTE PTR _var1$[esp-4], 0 sete al ret 0 _f ENDP 

And without optimization:

 _g PROC push ebp mov ebp, esp push ecx movzx eax, BYTE PTR _var1$[ebp] test eax, eax je SHORT $LN2@g mov BYTE PTR _var2$[ebp], 0 jmp SHORT $LN3@g $LN2@g: mov BYTE PTR _var2$[ebp], 1 $LN3@g: mov al, BYTE PTR _var2$[ebp] mov esp, ebp pop ebp ret 0 _g ENDP _f PROC push ebp mov ebp, esp sub esp, 8 movzx eax, BYTE PTR _var1$[ebp] test eax, eax jne SHORT $LN3@f mov DWORD PTR tv66[ebp], 1 jmp SHORT $LN4@f $LN3@f: mov DWORD PTR tv66[ebp], 0 $LN4@f: mov cl, BYTE PTR tv66[ebp] mov BYTE PTR _var2$[ebp], cl mov al, BYTE PTR _var2$[ebp] mov esp, ebp pop ebp ret 0 _f ENDP 

Again, there is a difference, but it is very small ..

If you optimize the functions themselves by declaring them as _fastcall , you get the code

 test cl, cl sete al 

(cf. with Open Watcom below).

Open Watcom behaved very funny (for it it was necessary to replace bool with int ):

 f_: L$1: test eax,eax sete al movzx eax,al ret g_: jmp L$1 

He generally threw a call to g in f ! :)

It did exactly the same thing in C ++ mode (optimization in both cases is by default).

 bool near f( bool ): L$1: test al,al sete al ret bool near g( bool ): jmp L$1 

With full optimization in C mode, he simply merged both functions:

 f_: g_: test eax,eax sete al movzx eax,al ret 
  • Not in C, but in C ++. And without pluses, instead of bool, you will have int and xor will be incorrect, because from 2 you need to get 0, not 3. Add for C? - Qwertiy
  • @Qwertiy It turned out, in general, the same thing ... - Harry
  • @Qwertiy I recommend to see what Open Watcom does - it's just a song: it makes jmp from g right in f . Already here 200% that the code is the same! :) - Harry
  • Why did push survive? - Qwertiy
  • @Qwertiy Ah, he inserts a stack check when calling without a key. I removed it - it is irrelevant, but push 'and forgot ... - Harry

In the case of negation, such a set of assembler commands should be created:

  • Loading into the register from RAM.
  • Inversion operation
  • Save to RAM

Moreover, 1 and 3 - not necessarily. It depends on what goes further and what was before.

When comparing (this is not necessarily the case, here the compiler can optimize and depending on the processor another code can work):

  • Loading in the register
  • Subtraction 1
  • Check flag on the sign and conditional transition on it
  • Saving in RAM new value.

As you can see, the difference is not very big. Conclusion?

a) Prefer clear and beautiful code instead of optimized.

b) First test the application either manually, or based on common sense, or profiler, and then optimize the bottlenecks.

  • I am writing for a microprocessor - this is the idea that the second option is much faster, I just do not know the assembler. - FoeNicks
  • Some dubious list with a set of commands. This is not a command. And the inversion operation does not exist. There will be a test , then jz , not? - Qwertiy
  • I do not claim to super accuracy, I assume only that the teams will be like that. And why does this not exist? Some processors have a command Not - Uraty

The so-called "optimization" of this code fragment will have absolutely no effect on the performance of your program. But on the readability and understanding of your program can greatly affect.

This code snippet

 if (var1) var2 = false; else var2 = true; 

looks very confusing. It is not immediately obvious that var2 is the negation of var1 .

To make it even more obvious that this code is confusing, imagine that in this place you have to exit the function. Then you have to write.

 if (var1) var2 = false; else var2 = true; return var2; 

This code snippet

 var2 = !var1; 

more clear. It immediately shows that the value of the variable var2 is the negation of the value of the variable var1 . With the additional clause return everything can be written in one line

 return !var1; 

Or if you want the variable var2 to get the value, then

 return var2 = !var1; 

Moreover, for example in C ++ without any additional inclusions, you can even write

 return not var1; 

Or

 return var2 = not var1; 

In C, it suffices to include the <iso646.h> header

Moreover, in the same C ++, you can declare variables in conditions. Therefore, if a variable is used only, for example, in the body of an if statement or in a loop, then you can write

 if ( bool var2 = not var1 ) { // using var2 } 

or

 while ( bool var2 = not var1 ) { //... } 

And even in C, where you cannot declare variables in conditions, it is nevertheless better to write

 bool var2; while ( var2 = !var1 ) { // changing of var1 } 
  • one
    Well, for microcontrollers it can affect. Moreover, both in terms of code volume and speed. However, if this is so, then it was worth immediately writing in assembly language and not suffering. - Qwertiy