This post compares different kinds of comparison operators in python with a microbenchmark.

Intro

Yesterday our team for the course Problem Solving and Engineering Design came across a peculiar statement in a piece of code. We were trying to optimize this code because it was a serious bottleneck in our program. The function which included the statement looked like so

def AddBits(byte_offset, bit_offset, bits, value):
  global Bit_Offset
  for i in range(bits):
    if p_version == 2:
      if (value & 0x01):
        Array[(byte_offset + ((bit_offset + Bit_Offset + i) / 8))] |= (
        0x01 << ((bit_offset + Bit_Offset + i) % 8));
    else:
      if (int(value) & 0x01):
        # Python3 Change: Cast to integer type right after "Array["
        Array[int(
          (byte_offset + ((bit_offset + Bit_Offset + i) / 8)))] |= (
        0x01 << ((bit_offset + Bit_Offset + i) % 8));
    value /= 2
  Bit_Offset += bits

We were wondering why value & 0x01 and int(value) & 0x01 were used instead of just simply value is True. We reasoned that if they used it in this way instead of the other (in this case) equal, more readable way, it has to be faster.

Hypothesis

We can divide the hypothesis in two parts:

  • Hypothesis 1: Using the bitwise operator & is faster than using is.
  • Hypothesis 2: Using 0x01 is faster when comparing than using True.

Setup

I created a microbenchmark with the following script using the timeit module:

import timeit

def run_benchmark_hypothesis():
    test_hex_true_bitwise = timeit.Timer("1 & 0x01")
    test_hex_true_normal = timeit.Timer("1 is 0x01")
    test_hex_true_equals_sign = timeit.Timer("1 == 0x01")
    test_normal_true_bitwise = timeit.Timer("1 & True")
    test_normal_true_normal = timeit.Timer("1 is True")
    test_normal_true_equals_sign = timeit.Timer("1 == True")

    print "Testing hypothesis 1: equals comparator (bitwise, normal, equals sign)"
    print "Testing hypothesis 2: True representation (hexadecimal, normal)"
    print "--------------------------------"
    print "HEXADECIMAL True (0x01): bitwise, normal, equals sign"
    print "bitwise:     " + str(test_hex_true_bitwise.timeit())
    print "normal:      " + str(test_hex_true_normal.timeit())
    print "equals sign: " + str(test_hex_true_equals_sign.timeit())
    print ""
    print "NORMAL True (True): bitwise, normal, equals sign"
    print "bitwise:     " + str(test_normal_true_bitwise.timeit())
    print "normal:      " + str(test_normal_true_normal.timeit())
    print "equals sign: " + str(test_normal_true_equals_sign.timeit())
    print ""

run_benchmark_hypothesis()

Results

The output of the microbenchmark above is:

Testing hypothesis 1: equals comparator (bitwise, normal, equals sign)
Testing hypothesis 2: True representation (hexadecimal, normal)
--------------------------------
HEXADECIMAL True (0x01): bitwise, normal, equals sign
bitwise:     0.0265879631042
normal:      0.0418701171875
equals sign: 0.0426430702209

NORMAL True (True): bitwise, normal, equals sign
bitwise:     0.0644659996033
normal:      0.0520520210266
equals sign: 0.0672380924225

Executing this script on different machines gives similar results.

Interpretation

When representing a True value in hexadecimal format 0x01 the bitwise equals operation 1 & 0x01 is the fastest. Both the normal equals comparison 1 is 0x01 and the equals sign comparison 1 == 0x01 are about the same execution time and are slower than the bitwise operation.

When using a normal representation for a True value, the normal equals comparison 1 is True is the fastest. The bitwise equals comparison 1 & True is considerably slower than the normal equals comparison. The slowest execution time is noted for the equals sign equals comparison 1 == True.

All execution times for the equals comparison using the hexadecimal representation are faster than the execution times for the equals comparison using the normal representation.

Conclusion

We can conclude that by using the bitwise equals comparison and the hexadecimal representation for True results in the fastest execution time. So both hypothesises are true. Using 1 & 0x01 can be twice as fast as the 'standard' equals comparison 1 is True.

Remarks

Instead of using the hexadecimal representation 0x01, we can also simply use the decimal representation 1.