http://www.perlmonks.org?node_id=301436


in reply to Re: Re: Re: Re: Re: Re: Perl Idioms Explained: && and || "Short Circuit" operators
in thread Perl Idioms Explained - && and || "Short Circuit" operators

Modern C compilers have the smarts built in to make intelligent decisions on what code to emit. In the case of your example of 2 widely spaced cases, it produces this

?live1@0: ; ; int main( int argc, char * argv[] ) { ; push ebp mov ebp,esp push ecx ; ; unsigned long selector; ; sscanf( argv[1], "%li", &selector ); ; @1: lea eax,dword ptr [ebp-4] push eax push offset s@ mov edx,dword ptr [ebp+12] push dword ptr [edx+4] call _sscanf add esp,12 ; ; ; switch ( selector ) { ; mov ecx,dword ptr [ebp-4] sub ecx,-2147483648 je short @5 sub ecx,-2147483647 jne short @2 ; ; case 1 : printf( "case 1\n" ); +break; ; push offset s@+4 call _printf pop ecx @8: pop ecx pop ebp ret ; ; case 2147483648 : printf( "case 2147483648\n" ); + break; ; @5: push offset s@+12 call _printf pop ecx @9: pop ecx pop ebp ret ; ; default : printf( "default\n" ); + break; ; @2: push offset s@+29 call _printf pop ecx ; ; } ; } ; @6: @7: pop ecx pop ebp ret _main endp _TEXT ends

Which as you can see, gets translated into a test and jump for the extreme case, another test and jump for the default case and a fall through for the 1 case.

If you combine a contiguous range and an extreme case, you get.

_TEXT segment dword public use32 'CODE' _main proc near ?live1@0: ; ; int main( int argc, char * argv[] ) { ; push ebp mov ebp,esp push ecx ; ; unsigned long selector; ; sscanf( argv[1], "%li", &selector ); ; @1: lea eax,dword ptr [ebp-4] push eax push offset s@ mov edx,dword ptr [ebp+12] push dword ptr [edx+4] call _sscanf add esp,12 ; ; ; switch ( selector ) { ; mov ecx,dword ptr [ebp-4] cmp ecx,3 jg short @11 je short @7 sub ecx,-2147483648 je short @10 sub ecx,-2147483647 je short @9 dec ecx je short @8 jmp short @2 @11: sub ecx,4 je short @6 dec ecx je short @5 dec ecx je short @4 jmp short @2 ; ; case 1 : printf( "case 1\n" ); +break; ; @9: push offset s@+4 call _printf pop ecx @14: pop ecx pop ebp ret ; ; case 2 : printf( "case 2\n" ); + break; ; @8: push offset s@+12 call _printf pop ecx @15: pop ecx pop ebp ret ; ; case 3 : printf( "case 3\n" ); + break; ; @7: push offset s@+20 call _printf pop ecx @16: pop ecx pop ebp ret ; ; case 4 : printf( "case 4\n" ); + break; ; @6: push offset s@+28 call _printf pop ecx @17: pop ecx pop ebp ret ; ; case 5 : printf( "case 5\n" ); + break; ; @5: push offset s@+36 call _printf pop ecx @18: pop ecx pop ebp ret ; ; case 6 : printf( "case 6\n" ); + break; ; @4: push offset s@+44 call _printf pop ecx @19: pop ecx pop ebp ret ; ; case 2147483648 : printf( "case 2147483648\n" ); + break; ; @10: push offset s@+52 call _printf pop ecx @20: pop ecx pop ebp ret ; ; default : printf( "default\n" ); + break; ; @2: push offset s@+69 call _printf pop ecx ; ; } ; } ; @12: @13: pop ecx pop ebp ret _main endp _TEXT ends

Which has been converted into a test cascade something like the original Perl code. However, this is before the optimiser has gone to work on it. Once the optimiser sees that code, it is quite likely that it will revert to using a dispatch table for the continuous cases with a single test for the extreme case. I having having trouble obtaining a post - optimisation disassembly that is readable. I'll try to update with that later.

As you might guess, if your range of cases is a widely spread set of discontiguous cases, then the result is you end up with cascaded tests just like the perl version. However, in almost every case (sic:) I remember using, I found some fairly simple function theat mapped the range to a contiguous or near-contiguous integer range. When this isn't the case, then I use an if/else cascade instead.


Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail
Hooray!

Replies are listed 'Best First'.
Re: Re+ :Perl Idioms Explained: && and || "Short Circuit" operators
by demerphq (Chancellor) on Oct 23, 2003 at 12:25 UTC

    So the rub of this is that switch() is not O(1). But that as often as possible the optimizer will do its best to convert it to be so. Which if you think about it means that from a programmers perspective its merely syntactic sugar for a chain of ifs. I say this becuase its just as reasonable to assume that the optimizer could figure out that a chain of ifs can be represented by a jump table and do the same optimization there.

    Also in your private message to me you mentioned that on occassion the compiler constructs a multi gigabyte executable to try to provide this "optimization". Which I would have thought _totally_ defeats the purpose given that the swap/thrash times involved to implement it would _far_ outweigh the execution times of a chained if.

    Anyway, many thanks to you and the other respondants about this. I have certainly learned something.


    ---
    demerphq

      First they ignore you, then they laugh at you, then they fight you, then you win.
      -- Gandhi


      I say this becuase its just as reasonable to assume that the optimizer could figure out that a chain of ifs can be represented by a jump table and do the same optimization there.

      Indeed, Perl4 did precisely that. We kept threatening to do it in Perl5, but nobody ever got around to doing it. Perl6 will need to do it--the switch statement was designed with the assumption that it would be optimized in the simple cases.