Skip to content

Instantly share code, notes, and snippets.

@PM2Ring
Created December 14, 2016 11:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PM2Ring/1addf93cc3976b699d31ff1bad45e4ed to your computer and use it in GitHub Desktop.
Save PM2Ring/1addf93cc3976b699d31ff1bad45e4ed to your computer and use it in GitHub Desktop.
Speed tests of various functions for counting the '1' bits in a 32 bit integer in Python 2 or Python 3
#!/usr/bin/env python3
''' Count bits in a 32 bit Python integer
Speed tests of various implementations, mostly
from http://stackoverflow.com/questions/9829578/fast-way-of-counting-bits-in-python
Some of these functions work on arbitrary input, but some are specifically for
32 bit integers, see the function sources for detail.
Written by PM 2Ring 2015.05.22
Python 2 / 3 version
Loops = 500, Repetitions = 3
bincount
[1.3069090540000161, 1.353041044000065, 1.362490262999927]
bincount_fmt
[1.9206439549998322, 1.9235854250000557, 2.0456352710000374]
count_set_bits
[12.044507647999808, 12.0926836000001, 12.120520207000027]
count_set_bits_a
[12.233064161999891, 12.240866340000139, 12.33954297199989]
number_of_set_bits
[2.0478857069999776, 2.0485297529999116, 2.0935126169999876]
kernighan_bitcount
[6.366286704999993, 6.408641941000042, 7.43474094499993]
count_bits
[2.6552196699999513, 2.6758667039998727, 2.696372879999899]
popcount32_table16
[1.014889951999976, 1.0157701009998163, 1.0245300270000826]
popcount32_table16a
[0.8907187499999054, 0.8914507599999979, 0.943135520000169]
acount16
[0.9405839329999708, 0.94409908800003, 0.9534000090000063]
acount16w
[0.8587064329999521, 0.8790086770000016, 0.8869148689998383]
popcount
[0.2948963090000234, 0.2960135650000666, 0.30662088400003995]
'''
from __future__ import print_function, division
from timeit import Timer
from random import seed, randrange
import array
try:
from gmpy import popcount
except ImportError:
print("Can't import gmpy, so can't test gmpy.popcount")
popcount = None
def bincount(n):
return bin(n).count("1")
# slightly slower
def bincount_fmt(n):
return format(n, 'b').count("1")
def count_set_bits(n):
result = 0
while n:
if n & 1:
result += 1
n >>= 1
return result
# slightly slower
def count_set_bits_a(n):
result = 0
while n:
result += n & 1
n >>= 1
return result
def kernighan_bitcount(n):
count = 0
while n:
count += 1
n &= n - 1
return count
def count_bits(n):
n = (n & 0x55555555) + ((n & 0xAAAAAAAA) >> 1)
n = (n & 0x33333333) + ((n & 0xCCCCCCCC) >> 2)
n = (n & 0x0F0F0F0F) + ((n & 0xF0F0F0F0) >> 4)
n = (n & 0x00FF00FF) + ((n & 0xFF00FF00) >> 8)
n = (n & 0x0000FFFF) + ((n ) >> 16)
return n
def number_of_set_bits(i):
i = i - ((i >> 1) & 0x55555555)
i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24
POPCOUNT_TABLE16 = [0] * 2**16
for index in range(len(POPCOUNT_TABLE16)):
POPCOUNT_TABLE16[index] = (index & 1) + POPCOUNT_TABLE16[index >> 1]
def popcount32_table16(v):
return (POPCOUNT_TABLE16[ v & 0xffff] +
POPCOUNT_TABLE16[(v >> 16) & 0xffff])
def popcount32_table16a(v):
return (POPCOUNT_TABLE16[v & 0xffff] +
POPCOUNT_TABLE16[v >> 16])
typecode = 'H'
atable = array.array(typecode, POPCOUNT_TABLE16)
def acount16(n):
return atable[n & 0xffff] + atable[n >> 16]
def make_acount():
atablew = array.array(typecode, POPCOUNT_TABLE16)
def acount16w(n):
return atablew[n & 0xffff] + atablew[n >> 16]
return acount16w
acount16w = make_acount()
funcs = (
bincount,
#bincount_fmt,
#count_set_bits,
#count_set_bits_a,
#number_of_set_bits,
#kernighan_bitcount,
#count_bits,
#popcount32_table16,
popcount32_table16a,
#acount16,
acount16w,
)
if popcount:
funcs += (popcount,)
seed(137)
nsize = 1024
nums = [randrange(0, 1<<32) for _ in range(nsize)]
def verify():
''' Verify that the functions actually perform as intended '''
print('Verifying...')
a = [bincount(n) for n in nums]
for func in funcs[1:]:
fname = func.__name__
print('%s' % fname, end=' ')
print(all(func(n) == a[i] for i, n in enumerate(nums)))
print()
def time_test(loops, reps):
''' Print timing stats for all the functions '''
print('Timing tests\nLoops = %d, Repetitions = %d' % (loops, reps))
for func in funcs:
fname = func.__name__
print('\n%s' % fname)
setup = 'from __main__ import nums, %s' % fname
t = Timer('[%s(n) for n in nums]' % fname, setup)
r = t.repeat(reps, loops)
r.sort()
print(r)
verify()
time_test(loops=500, reps=3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment