flotothemoon
flotothemoon

Reputation: 1892

Why are bitwise operators slower at comparing booleans than the "normal" ones in Java?

Assume the following: You got two functions, both doing basically the same thing, which is comparing two random booleans with AND and OR operators. But the one function does this with the normal conditional operators && and ||, the other one with the bitwise operators & and |.

I thought that those two functions would of course take the same time to complete, but they dont. The one with bitwise comparison takes one fifth more time than the one with the "normal" conditional operator.

I was confused and did some research and found the following definition for the conditional operators on the Java documentation from Oracle:

The && and || operators perform Conditional-AND and Conditional-OR operations on two boolean expressions. These operators exhibit "short-circuiting" behavior, which means that the second operand is evaluated only if needed.
&& Conditional-AND
|| Conditional-OR

and for the bitwise operator:

The Java programming language also provides operators that perform bitwise and bit shift operations on integral types. 
The bitwise & operator performs a bitwise AND operation.

The bitwise | operator performs a bitwise inclusive OR operation.

Well, those definitions didn't really satisfy me and my question was still not answered. So I did some more research and found a question here on stackoverflow about the effect of bitwise operators on booleans in Java (Effect of a Bitwise Operator on a Boolean in Java), but that didnt really answer my question either.

Here is the code I tested it with, it uses a random number generator to get random booleans to compare with one another and loops over that a loooot of times.

import java.util.Random;

public class Main {

    private static final long LOOPS = 100000000L;
    private static Random rng = new Random();

    public static final void main(String[] args)
    {
        System.out.println("Bitwise operator: "+loopWithBitwiseOperator(LOOPS));
        System.out.println("Normal operator: "+loopWithNormalOperator(LOOPS));
    }

    public static long loopWithNormalOperator(long loops)
    {
        long startTime = System.currentTimeMillis();

        for (long i = 0L; i < loops; i++)
        {
            if (rng.nextBoolean() || rng.nextBoolean())
            {
                i++;
            }

            if (rng.nextBoolean() && rng.nextBoolean())
            {
                i++;
            }
        }

        return System.currentTimeMillis()-startTime;
    }

    public static long loopWithBitwiseOperator(long loops)
    {
        long startTime = System.currentTimeMillis();

        for (long i = 0L; i < loops; i++)
        {
            if (rng.nextBoolean() | rng.nextBoolean())
            {
                i++;
            }

            if (rng.nextBoolean() & rng.nextBoolean())
            {
                i++;
            }
        }

        return System.currentTimeMillis()-startTime;
    }
}

I received the following output:

Bitwise operator: 2502
Normal operator: 1806

So the bitwise operator is in fact slower than the "normal" one. My first idea was that maybe the generated boolean by the random number generator is somehow treated different at runtime and when compared with another boolean it actually bitwisely compares more than needed, as it is undefined how it is actually saved. But then I thought that it might happen because of the "short-circuiting" mechanism for conditional operators, which maybe doesnt apply to bitwise operators which probably actually need both values for comparison.

Is my assumption correct? Why does this happen? Another example-specific behavior that doesnt represent the actual behavior of Java's logical operators?

As always, thanks for any help and clarification in advance.

EDIT:

As requested in the comments, I tried to switch the calls around and include some kind of a warm up phase, but still not much of a change:

Normal operator: 1801
Bitwise operator: 2433

Upvotes: 0

Views: 939

Answers (1)

Patricia Shanahan
Patricia Shanahan

Reputation: 26185

Louis Wasserman's comment is the correct diagnosis. The number of calls to the random number generator strongly affects the performance, so short-circuiting is important. I modified the program to count the numbers of calls using a couple of methods like this:

private static boolean normalCountedRandom() {
  normalCount++;
  return rng.nextBoolean();
}

I also did multiple measurements in each run to eliminate any issue of start-up effects. A typical output was:

Bitwise operator: 1985
Normal operator: 1560
Bitwise operator: 1967
Normal operator: 1547
Bitwise operator: 2046
Normal operator: 1557
Bitwise operator: 2009
Normal operator: 1553
Bitwise Random calls: 800006428
Normal Random calls: 600030931

The 4:3 ratio is as expected. Each if does one compulsory call. If using a short circuit operator, only half of those will result in a second call, for an expected 1.5 calls per if, compared to 2 per if without the short-circuit. This ratio is similar to the performance ratio, assuming the calls account for most, but not all, of the time.

The short-circuit behavior is documented in the JLS. Technically, when applied to boolean operands, & and | are Boolean Logical Operators. The "normal" operators are Conditional-And and Conditional-Or Operators.

It is generally better to avoid having the number of iterations depend on a Random result, although in this case, with enough iterations, the variance between runs is small.

Upvotes: 2

Related Questions