homer512 icon

bit counting benchmark

homer512 | PRO | 06/05/14 05:13:12 PM UTC | 0 ⭐ | 752 👁️ | Never ⏰ | []
Python |

3.28 KB

|

None

|

0 👍

/

0 👎

#!/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