0

Im programming some ring buffers and this question came to me several times.

Suppouse we have a counter and we need to reset after certain count. Ive seen several examples of ring buffers (mostly audio, wraping around r/w pointers), that do this:

x++;
if (x  == SOME_NUMBER ){ // Reseting counter
    x -= x;
}

is there any difference/preference in doing this:

x++;
if (x  == SOME_NUMBER ){ // Reseting counter
    x = 0;
}

?

This question applies to almost all kind of variable resets. In my case, besides ring buiffers, im also reseting a counter that do an average, so after i made all my measures, i reset that counter.

Besides the fact that the result may be the same (x reseting to zero), there may be some difference between one approach and the other. Is there any preference?

SISKO
  • 43
  • 3
  • 1
    x -= x you have 2 operation substation and assignment; with x = 0 you only do an assignment, and it's more clear to understand – alon Jul 11 '19 at 13:08

2 Answers2

1

Consider those slightly modified versions of your snippets

void f(int n)
{
    int x = 0;
    for (;;)
    {
        ++x;
        if (x == n ) {   // Reseting counter
            x -= x;
        }
        // Ending condition to avoid UB
        if ( x == 42 )
            return;
    }
}

void g(int n)
{
    int x = 0;
    for (;;)
    {
        ++x;
        if (x == n ) {
            x = 0;
        }

        if ( x == 42 )
            return;
    }
}

If you look at the generated assembly (e.g. using Compiler Explorer) you'll notice how modern optimizing compilers can take advantage of the as-if rule.

Clang (with -O2) generates the same machine code for both functions. It uses

xor     eax, eax

To load a zero into a register and then

cmove   ecx, eax

to "reset" the other register when needed.

Gcc just creates f() and then g() becomes

jmp     f(int)

That said

Is there any preference?

A common guideline is to write the more readable and maintainable code and to explore possible optimizations only after having profiled it.

In most cases I'd use the x = 0; version, because it conveys the intent better, IMHO. I can only think of a couple of reasons to adopt the x -= x; one:

  • It does not rely on "magic numbers". However, that would be the case for the 42 literal in my snippet, 0 is an exceptional case.
  • It doesn't need any implicit conversion. Consider any case where x is not an int.
  • There may be some architectures/toolchains where it actually delivers faster code. I can't think of any, but that's immaterial.
Bob__
  • 12,361
  • 3
  • 28
  • 42
0

The difference is in the number of operations: x -= x is subtraction and assignment, whereas x = 0 is just an assignment. Other than the number of CPU cycles, this affects behavior if x is accessible from other threads.

A simple assignment x = 0 is much clearer as well IMO.

  • 1
    If `x` is accessed from other threads without synchronization this is UB anyway. – Quentin Jul 11 '19 at 13:26
  • Is it UB if the assignment is atomic and the other thread only _reads_ `x`? – Raven221221221 Jul 11 '19 at 14:32
  • If `x` is an `std::atomic`, that's fine. Note that in that case `-=` would be atomic as well, so still the same behaviour as `=`. – Quentin Jul 11 '19 at 14:37
  • Need to brush up on what counts as a race condition.. I thought that if you have atomic writes in one thread and atomic reads in the other, that would not be UB. +1 for keeping me honest! – Raven221221221 Jul 11 '19 at 15:17