4

Consider the following two code examples:

Example 1.

public void setValue(int value)
{
    mValue = value;
}

Example 2.

public void setValue(int value)
{
    if (mValue != value)
    {
        mValue = value;
    }
}

Pretend you've been tasked to optimise some Java code to the absolute max, beyond all common sense.

Would the second code example be an optimisation of the first? By that I mean, is there any difference (no matter how miniscule) between an if condition check and an int assignment at the lowest levels of Java or the JVM?

XåpplI'-I0llwlg'I -
  • 21,649
  • 28
  • 102
  • 151

4 Answers4

9

The second is likely to be an anti-optimization since on modern processors branching tends to be expensive (primarily because branch misprediction is extremely costly).

The only way to find out for sure is to profile your code on representative inputs. See How do I write a correct micro-benchmark in Java? for some pointers on how to set up a meaningful micro-benchmark.

Bear in mind that if the code in question doesn't account for a significant fraction of the overall CPU time, all optimizations in the world won't make a lot of difference to the overall performance of your applications.

Community
  • 1
  • 1
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 3
    If you are very lucky, the conditional branch will be easy to predict, and be no more expensive than a typical operation. If you are unlucky, it will have to flush the pipeline on a significant fraction of the times it is executed. – Patricia Shanahan Jan 05 '13 at 20:37
  • The point made by @yshavit below holds for all concurrent access, regardless of the field being volatile. You are trading a branch for a potentially pointless write-miss, which may be a good trade for a given probability of the field changing. – Nitsan Wakart Nov 16 '13 at 07:40
3

NPE has good advice. One comment I'll add is that probably also depends on whether mValue is volatile, and what hardware you're running it on. For volatiles, a write can be several times more expensive than a read. One some hardware (I believe many mobile devices, for instance) a volatile read is pretty far from cheap, so that difference probably goes down.

yshavit
  • 42,327
  • 7
  • 87
  • 124
1

You could try compiling both and then use a java bytecode disassembler to check the difference. Wikipedia JVM instruction listings might be a good spot to start.

From what I understand of the compilers and VMs I've worked with, the second code snippet will likely be a (minuscule) bit slower due to the extra comparison op.

If your JVM implementation has an optimizer, it will probably be able to reduce your first code sample to a one-op, whereas the second snippet will require at least one or two more ops to account for the comparison op.

Philip Conrad
  • 1,451
  • 1
  • 13
  • 22
0

Good coding practices says: "delay optimizations before last moment"

But code test says:

  • TOTAL(1)=1000000000 1420ms TOTAL(2)=1000000000 2155ms
  • TOTAL(1)=1000000000 1374ms TOTAL(2)=1000000000 1884ms
  • TOTAL(1)=1000000000 1379ms TOTAL(2)=1000000000 2072ms

Compare & Set is NOT better.

// Test Code
public class CompareAndSet {
    int mValue;

    // 1
    public void setValue1(int value) {
        mValue = value;
    }

    // 2
    public void setValue2(int value) {
        if (mValue != value) {
            mValue = value;
        }
    }

    public static void main(String[] args) {
        final int TOTAL = (int) 1e9;
        long ts;
        for (int j = 0; j < 3; j++) {
            // 1
            {
                ts = System.currentTimeMillis();
                CompareAndSet cs = new CompareAndSet();
                for (int i = 0; i < TOTAL; i++) {
                    cs.setValue1(i);
                }
                System.out.println("TOTAL(1)=" + TOTAL + " "
                        + (System.currentTimeMillis() - ts) + "ms");
            }
            // 2
            {
                ts = System.currentTimeMillis();
                CompareAndSet cs = new CompareAndSet();
                for (int i = 0; i < TOTAL; i++) {
                    cs.setValue2(i);
                }
                System.out.println("TOTAL(2)=" + TOTAL + " "
                        + (System.currentTimeMillis() - ts) + "ms");
            }
        }
    }

}
ggrandes
  • 2,067
  • 22
  • 16
  • If change "setValueX(i)" to setValueX(1), the times are (compare&set always are NOT better): - TOTAL(1)=1000000000 1390ms TOTAL(2)=1000000000 1386ms - TOTAL(1)=1000000000 1354ms TOTAL(2)=1000000000 1353ms - TOTAL(1)=1000000000 1398ms TOTAL(2)=1000000000 1389ms – ggrandes Jan 05 '13 at 20:54
  • 1
    There are several biases in your test, in particular: (i) you don't allow the method to be compiled before measuring results (ii) `mValue != value` is always true => no branch mispredictions. – assylias Jan 05 '13 at 20:54
  • 1
    Also note that on my machine, I get `TOTAL(1)=1000000000 0ms` because you don't do anything with the results and the whole loop gets optimised away by the compiler... – assylias Jan 05 '13 at 20:58
  • test are not perfect, but orientative yes, compare & set is not better... ;-) – ggrandes Jan 05 '13 at 20:58
  • Improperly built tests can actually be misleading (which is orientative, but in the wrong direction)! – assylias Jan 05 '13 at 20:59
  • ok, your machine are too machine :-P – ggrandes Jan 05 '13 at 20:59
  • Use JMH to avoid the pitfalls – Nitsan Wakart Nov 16 '13 at 07:42