2

I got curious about the performance implications of formating OR-based logic differently, so I ran a simple and probably somewhat unscientific test. I ran the code below 10 times with the if-statement set to true (to trigger Case 1) and 10 times with it set to false (to trigger Case 2). I used this site to test the code and relied on its reported "absolute running time".

The result I got was that Case 1 takes an average of 0.354 seconds to run, while Case 2 takes an average of 0.442 seconds to run. The numbers were fairly consistent individually - in Case 1 they varied within a range of 0.04 and in Case 2 they varied within a range of 0.09, and there was no overlap at all, so it seems at first blush to be a clear difference.

Both cases yield the same result for counter - 38571428 in this case, but the same value for other toCount values I tried as well. My understanding is that else-if statements are not evaluated if a previous if or else-if statement evaluated to true, so from a logic standpoint I think the two cases are doing the same thing.

I don't have a good intuitive sense of why Case 1 performs better than Case 2. Could someone shine some light on this? I'm interested in a general sense, for the sake of better understanding how different ways of structuring C# logic might impact performance, so I'm hoping there might also be underlying principles to be understood by this example, but even just the explanation for this case seems interesting.

int counter = 0;
int toCount = 50000000;
//Case 1
if (false)
{
    for (int iNum = 0; iNum < toCount; ++iNum)
    {
        if (iNum % 2 == 0 || iNum % 3 == 0 || iNum % 5 == 0 || iNum % 7 == 0)
        {
            ++counter;
        }
    }
}
//Case 2
else
{
    for (int iNum = 0; iNum < toCount; ++iNum)
    {
        if (iNum % 2 == 0)
        {
            ++counter;
        }
        else if (iNum % 3 == 0)
        {
            ++counter;
        }
        else if (iNum % 5 == 0)
        {
            ++counter;
        }
        else if (iNum % 7 == 0)
        {
            ++counter;
        }
    }
}
Console.WriteLine("Result: " + counter.ToString());
GarrickW
  • 2,181
  • 5
  • 31
  • 38
  • What optimization level did you compile and run with? – Ben Voigt Nov 16 '20 at 16:00
  • Note that an actually smart compiler would unroll chunks of 2*3*5*7 iterations, discover that always results in the same number of matches, and only actually have to loop through the final partial chunk. – Ben Voigt Nov 16 '20 at 16:03
  • 1
    The two versions compile to [different IL code](https://sharplab.io/#gist:66006412ffbbe6a5a354b170c23b11d9): the ifs are significantly longer, and thus its JITted assembly is also longer. The number of executed instructions should not really differ but it can be enough for not inlining the second version (you might try to apply `[MethodImpl(MethodImplOptions.AggressiveInlining)]` to your method and check the difference again). And maybe the CPU can also optimize the pipelines for the JITted code of the first version better. – György Kőszeg Nov 16 '20 at 16:37

2 Answers2

1

It depends. You have to thoroughly benchmark your specific use case. But, without knowing the details, I would say: neither

Start by checking the generated IL code and/or JIT Asm at sharplap.io. It should give you some hints. Looks like Case1 yields smaller code size. That might affect inlining during optimization.

You could also try to have four separate for-loops to possibly maximize performance. It all depends on the CPU cache. This concept has been discussed before. I suggest you read these answers/discussions:

l33t
  • 18,692
  • 16
  • 103
  • 180
-3

both if statements and or statements have there pros and cons if statement is if this happen do this and a or statement is like saying do this or that and you giving the computer a decision between the two for or statements. Just try to put your code in plain english or whatever you native tongue is it makes it simple.

programmr
  • 5
  • 3