Want to show your appreciation? Please to my charity.

Tuesday, October 08, 2013

If/Else Statement vs. Ternary Operator

The question of which one is faster, the ternary operator or if/else statement, was asked again and again. The canned answer is: you don’t care, don’t even think about performance here, just choose the one that is most readable in the context you use.

But leave aside about the argument of which one is more readable. If I’m going to call it hundred millions of times, the performance difference might be matter to me, right? I was again asked about this question recently so I decided to find this out. Only test can tell the truth.

Test Setup

We define each run as repeatedly transmitting data from an array with 10 elements to another array multi-million times. When the data are transmitted from one array to another, we also transform the date. We used different methods to transform the data so we can compare the time difference between different methods. We also capture the baseline performance. A baseline does very same thing as the regular run but with not data transformation.

The code for every run is somewhat like below.

   1:          @Override
   2:          void run() {
   3:              int a[] = source;
   4:              int b[] = target;
   5:              for (int i = LOOP_COUNT; i > 0; i--) {
   6:                  for (int j = ELEMENT_END; j >= 0; j--) {
   7:                      // copy and transform the data from a to b
   8:                  }
   9:              }
  10:          }

We execute the run 20 times to warm up the system and run it another 100 times with time measured for each run.

   1:          // warn up
   2:          for (int j = 20; j > 0; j--) {
   3:              for (Fixture f : fixtures)
   4:                  f.run();
   5:          }
   6:   
   7:          // run and measure it
   8:          for (int j = 100; j > 0; j--) {
   9:              for (Fixture f : fixtures)
  10:                  f.runMeasured();
  11:          }

And the code to measure the performance.

   1:      void runMeasured() {
   2:          long start = System.nanoTime();
   3:          run();
   4:          histogram.update(System.nanoTime() - start);
   5:      }

You can find the source code on github.

And all the test run was on a Lenovo X220 Laptop with Core i5-2520M @ 2.5GHz, Running Windows 7 64bit and JRE 1.7 64bit.

Absolute Value Test

The first test is to transform the data in the source array into their absolute value and then store them in target array. The data in the source array were purposely set to be half positive and half negative.

The similar tests were done in two different ways. Both use a base class and have each derived class overrides a method. The difference is that, one have the loop logic common in base class and let the derived class only override the method that does the absolute value calculation, another repeats the loop logic in each derived class and have the absolute value calculation inlined inside the loop.

Using Overridden Methods

The test is captured in RunnerAbsOverride class. Below is the run logic

   1:          void run() {
   2:              int a[] = source;
   3:              int b[] = target;
   4:              for (int i = LOOP_COUNT; i > 0; i--) {
   5:                  for (int j = ELEMENT_END; j >= 0; j--) {
   6:                      int x = a[j];
   7:                      b[j] = abs(x);
   8:                  }
   9:              }
  10:          }

We compare the performance different by using different implementations of the abs methods.

Baseline

The baseline implementation doesn’t really compute the absolute value. It simply returns itself without no data transformation.

            return x;
Using Math.abs

This method calls the JDK build in abs method in Math class to transform the date into absolute value.

            return Math.abs(x);
Using ternary operator

Here we use ternary operator to compute the absolute value of x.

            return x >= 0 ? x : -x;
Using if/else statement

At last, we use if/else statement to return x if it is positive and –x if x is negative.

            if (x >= 0) return x;
            else return -x;
Test Result

Below is the result of calculating absolute value in an overridden method of different implementations. Time is the nano seconds taken to transform and copy the array.

Method,    Mean,   Min,   Max, Median,  75%
Baseline,  36.4,  35.6,  39.0,  36.2,  36.6
MathAbs ,  40.6,  39.6,  49.9,  40.2,  40.7
Ternary ,  40.3,  39.6,  42.2,  40.2,  40.6
IfElse  ,  40.4,  39.5,  43.9,  40.2,  40.7

We observed three things here

  1. Comparing to the baseline, computing the absolute value of an integer takes additional 11% more time on top of baseline’s looping and a method call.
  2. All three methods of calculating an absolute value has same performance.
  3. We didn’t see any difference of using the built in Math.abs. Which means the static method is inlined and nothing else special was done to this JDK method.

Inlining the Code

What if we inline the code directly in the loop? Let’s repeat the loop below in every implementation and replace the comment with the different methods of computing the absolute values. This test is captured in RunnerAbsInline class.

   1:          final void run() {
   2:              final int a[] = source;
   3:              final int b[] = target;
   4:              for (int i = LOOP_COUNT; i > 0; i--) {
   5:                  for (int j = ELEMENT_END; j >= 0; j--) {
   6:                     // compute absolute value of a[j] then assign it to b[j]
   7:                  }
   8:              }
   9:          }
Baseline
                    b[j] = a[j];
Using Math.abs
                    b[j] = Math.abs(a[j]);
Using ternary operator
                    b[j] = a[j] >= 0 ? a[j] : -a[j];
Using if/else statement
                    if (a[j] >= 0) b[j] = a[j];
                    else b[j] = -a[j];
Using if statement without else

When it is inlined, it allow us to use a single if statement to get the absolute value.

                    int x = a[j];
                    if (x < 0) x = -x;
                    b[j] = x;
Test result

Below is the result of calculating absolute value inlined in the loop of different implementations. Time is the nano seconds taken to transform and copy the array.

Method,    Mean,   Min,   Max, Median,  75%
Baseline,   1.7,   1.6,   3.0,   1.7,   1.7
MathAbs ,   7.0,   6.7,   8.6,   6.9,   7.0
Ternary ,   7.0,   6.7,   8.4,   6.9,   7.0
IfElse  ,   8.6,   8.4,   9.6,   8.5,   8.6
IfOnly  ,   6.9,   6.7,   9.0,   6.9,   7.0

The numbers above tells us that

  1. Using if/else statement is 30% slower than other methods in average.
  2. We have added a if statement only implementation is at the same speed as Math.abs() and ternary operator.
  3. Again, we didn’t see the difference between using Math.abs vs. the ternary operation here.
  4. The overhead of method invocation is much higher than any of the method we used.

Number Ordering Test

The 2nd test is compare the numbers in the two adjacent elements of the source array, then store the two number to the target array in ascending order. The data in the source array were carefully set so that the opportunity is the same for the left element of the adjacent two being smaller or larger.

Again, like we did in the Absolute Value test, we’ll test in two different ways. One have the logic in its own method and another to have the logic inlined in the loop.

Using Overridden Methods

The test is captured in RunnerOrderingOverride class. Below is the run logic

   1:          void run() {
   2:              int a[] = source;
   3:              for (int i = LOOP_COUNT; i > 0; i--) {
   4:                  for (int j = ELEMENT_END; j >= 0; j--) {
   5:                      compareSet(a[j], a[j + 1], j);
   6:                  }
   7:              }
   8:          }

We compare the performance different by using different implementations of the compareSet methods.

Baseline

The baseline implementation doesn’t really compare the value. It simply sets the value in the original order.

            int b[] = target;
            b[index++] = x;
            b[index] = y;
Using Math.abs

This try to avoid the if statement by computing the difference and sum of the two number, and then derive the smaller one and larger from the sum and different. The difference is obtained by subtracting one from another and then taking the absolute value.

            int b[] = target;
            int diff = Math.abs(x - y);
            int sum = x + y;
            b[index++] = sum - diff;
            b[index] = sum + diff;
Using ternary operator

This is very similar to Math.abs, except that the ternary operator is used instead of Math.abs.

            int b[] = target;
            int diff = x >= y ? x - y : y - x;
            int sum = x + y;
            b[index++] = sum - diff;
            b[index] = sum + diff;
Using if/else statement

This uses straightforward if/else statement.

            int b[] = target;
            if (x >= y) {
                b[index++] = y;
                b[index] = x;
            } else {
                b[index++] = x;
                b[index] = y;
            }
Test Result

Below is the result of ordering the numbers in an overridden method of different implementations. The unit is nano second.

Method,    Mean,   Min,   Max, Median,  75%
Baseline,  54.3,  52.6,  66.4,  53.9,  54.7
MathAbs ,  66.3,  64.2,  79.2,  65.6,  66.3
Ternary ,  62.2,  60.2,  79.1,  61.2,  62.0
IfElse  ,  58.9,  56.3,  78.6,  58.0,  58.6

It turned out that the straightforward if/else statement is the fastest. The attempt of replacing it with ternary operator plus multiple steps of integer computation doesn’t really outperform if/else statement. It is actually much slower.

This time, Ternary is faster than Match.abs because it eliminated the negate operation in the Math.abs when x < y.

Inlining the Code

Again, let’s re-test the logic by inlining them in the loop. We repeat the loop below in every implementation and replace the comment with the different methods of ordering the number. This test is captured in RunnerOrderingInline class.

   1:          final void run() {
   2:              int a[] = source;
   3:              int b[] = target;
   4:              for (int i = LOOP_COUNT; i > 0; i--) {
   5:                  for (int j = ELEMENT_END; j >= 0; j--) {
   6:                      final int x = a[j];
   7:                      final int y = a[j + 1];
   8:                      // set the smaller one of x and y to b[j] and the larger one to b[j+1]
   9:                  }
  10:              }
  11:          }
  12:      }
Baseline

Baseline simply assigns the value without ordering them.

                    b[j] = x;
                    b[j + 1] = y;
Using Math.abs
                    int diff = Math.abs(x - y);
                    final int sum = x + y;
                    b[j] = sum - diff;
                    b[j + 1] = sum + diff;
Using ternary operator
                    final int diff = x >= y ? x - y : y - x;
                    final int sum = x + y;
                    b[j] = sum - diff;
                    b[j + 1] = sum + diff;
Using if/else statement
                    if (x >= y) {
                        b[j] = x;
                        b[j + 1] = y;
                    } else {
                        b[j] = y;
                        b[j + 1] = x;
                    }
Test result

Below is the result of ordering the numbers inlined in the loop of different implementations. Unit is nano seconds.

Method,    Mean,   Min,   Max, Median,  75%
Baseline,   3.8,   3.5,   4.9,   3.6,   3.7
MathAbs ,  18.3,  17.3,  22.6,  17.9,  18.5
Ternary ,  13.9,  13.0,  19.0,  13.5,  14.0
IfElse  ,  11.1,  10.6,  13.6,  11.0,  11.2

The result is same as the override method test except it is overall faster because of the inlined code. So method invocation is much more expensive than any of those.

Conclusion

  1. There are no fundamental difference between ternary and if/else.
  2. Ternary is faster then if/else as long as no additional computation is required to convert the logic to us ternary. When it is a simply ternary operation, it has better readability as well.
  3. If only statement is faster than if/else, so if the logic doesn’t require an else statement, do use it.
  4. Don’t try to outsmart. Adding additional operations to replace if/else with ternary lead you to the conversed result. In this case, use straightforward if/else gets you better performance and readability.
  5. JDK doesn’t do native optimization to Math.abs other than inlined the static method call almost everywhere. It is not like others suggested that JDK does native optimization for built in functions.
  6. Comparing the different between if/else and ternary, method invocation is every expensive.