Compiling swap()

in #programming6 years ago (edited)

"How do I swap two variables without using a temporary variable?" is one of the dumber programming questions. Please tell me you don't use this in interviews. There's the sensible way, the way using XOR that everybody "clever" knows, and an even worse implementation that relies upon undefined behavior.

Standalone compilation

What do these versions actually compile to? Let's use the Compiler Explorer to find out.

Source code, using C++ references:

void swap( int &a, int &b ) {
    int t = a;
    a = b;
    b = t;
}

void farTooCleverSwap( int &a, int &b ) {
    a ^= b;
    b ^= a;
    a ^= b;
}

void stupidSwap( int &a, int &b) {
    a = a + b;
    b = a - b;
    a = a - b;
}

When we compile these under GCC 8.2 with the -O3 flag, we get

As promised, the versions without temporaries use one fewer register. The downside is that they now have less instruction-level parallelism, because each instruction depends on the one immediately before... and the code is larger, too. That edx register is caller-saved, so the calling function can't make use of the fact that it's unused anyway.

In-place compilation

OK, you might argue, what about when the function is inlined? Then register pressure might be relevant, and the XORs could be more efficient than spilling a register to memory.

Good luck finding such a case. In fact, both GCC and clang are smart enough to know that all three functions are swaps and eliminate them altogether if possible.

Here's a test function:

int testFunc( int a, int b ) {
    printf( "%d %d\n", a, b );
    swap( a, b );
    printf( "%d %d\n", a, b );
    farTooCleverSwap( a, b );
    printf( "%d %d\n", a, b );
    stupidSwap( a, b );
    printf( "%d %d\n", a, b );
}

and the compiler output from clang 6.0.0, with -O3

testFunc(int, int):                          # testFunc(int, int)
        push    rbp
        push    rbx
        push    rax
        mov     ebx, esi
        mov     ebp, edi
        mov     edi, offset .L.str
        xor     eax, eax
        mov     esi, ebp
        mov     edx, ebx
        call    printf
        mov     edi, offset .L.str
        xor     eax, eax
        mov     esi, ebx
        mov     edx, ebp
        call    printf
        mov     edi, offset .L.str
        xor     eax, eax
        mov     esi, ebp
        mov     edx, ebx
        call    printf
        mov     edi, offset .L.str
        xor     eax, eax
        mov     esi, ebx
        mov     edx, ebp
        call    printf
.L.str:
        .asciz  "%d %d\n"

What happened here? The compiler stashed a and b in the ebx and ebp registers. Then it just pulls them out in whatever order are necessary to call printf. All your clever work to avoid introducing an extra register use, and the compiler went with two temporaries! Well, they were probably necessary anyway to save the arguments across the function call, but that just shows how useless trying to optimize away register usage is, when we're starting from C. Let the compiler worry about its register usage.

In fact, an earlier attempt to do a demonstration failed because I used constants to initialize a and b and the compiler just chose to load them new each time:

testFunc():                           # @testFunc()
        push    rax
        mov     edi, offset .L.str
        mov     esi, 3
        mov     edx, 5
        xor     eax, eax
        call    printf
        mov     edi, offset .L.str
        mov     esi, 5
        mov     edx, 3
        xor     eax, eax
        call    printf
        ...

Inspiration: https://www.quora.com/Can-I-swap-without-any-extra-variable-in-C/answer/Sergey-Zubkov-1 (and a later answer of his which did more or less the same thing.)

Addendum

In Python (or other languages with decent tuple support) the matter doesn't arise. Simply do

x,y = y,x

Is there a C++ version of this? std::pair has an inline swap method and of course the function template std::swap is better than writing your own--- it has specializations for all the containers you might think of swapping. Let's look at an example of that, here's a C++ function that swaps two vectors:

and here's the compiled version (color-coded so you can see that the green part is the 'if' statement and the white part is the swap.

Sort:  

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!



This post has been voted on by the steemstem curation team and voting trail.

There is more to SteemSTEM than just writing posts, check here for some more tips on being a community member. You can also join our discord here to get to know the rest of the community!

Coin Marketplace

STEEM 0.26
TRX 0.11
JST 0.033
BTC 64014.44
ETH 3064.06
USDT 1.00
SBD 3.86