#!/usr/bin/python3
"""Tests different algorithms to count the number of 1 bits in an integer
Python 3.3, Linux x86_64, Intel Core i5 CPU M 450 @ 2.40GHz:
str_count 6.04 µs
func_count 43.54 µs
naive_count 19.24 µs
wegner_count 9.21 µs
lut_count 5.21 µs
"""
import timeit
import time
import random
def str_count(numbers):
"""Uses string operations"""
return [bin(number).count('1') for number in numbers]
def func_count(numbers):
"""Naive counting using bitwise operations and just one statement"""
return [sum((number & 1 << bit) >> bit
for bit
in range(number.bit_length()))
for number in numbers]
def naive_count(numbers):
"""Naive counting in a loop"""
def gen():
for number in numbers:
count = 0
while number:
count += number & 1
number >>= 1
yield count
return list(gen())
def wegner_count(numbers):
"""Method of Peter Wegner in CACM 3 (1960), 322"""
def gen():
for number in numbers:
count = 0
while number:
count += 1
number &= number - 1
yield count
return list(gen())
def _gen_lut():
"""Generates a lookup table for lut_count"""
lut = [0] * 256
for index in range(256):
lut[index] = (index & 1) + lut[index >> 1]
return tuple(lut)
_lut = _gen_lut()
def lut_count(numbers):
"""Uses a lookup table
See http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable
"""
lut = _lut
def gen():
for number in numbers:
count = 0
while number:
count += lut[number & 0xff]
number >>= 8
yield count
return list(gen())
def _gen_nums():
"""Creates random integers emphasizing numbers < 1000"""
distribution = random.expovariate
lambda_ = 0.001
return [int(distribution(lambda_)) for _ in range(10000)]
samples = _gen_nums()
def test(methods):
"""Checks that given methods for equality against samples"""
results = (method(samples) for method in methods)
trusted = next(results)
wrongs = (method.__name__ for method, result in zip(methods[1:], results)
if result != trusted)
for name in wrongs:
print("%s is wrong" % name)
def main():
methods = (str_count, func_count, naive_count, wegner_count, lut_count)
test(methods)
methodnames = [method.__name__ for method in methods]
setups = ("from __main__ import %s, samples" % method
for method in methodnames)
statements = ("%s(samples)" % method for method in methodnames)
number = 10000000 // len(samples)
times = (timeit.timeit(setup = setup,
stmt = statement,
number = number,
timer = time.process_time,
)
for setup, statement in zip(setups, statements))
results = ("%s\t%.2f µs" % (method, time) for method, time
in zip(methodnames, times))
for result in results:
print(result)
if __name__ == '__main__':
main()
Comments