Created
December 14, 2016 11:14
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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