#!/usr/bin/env python # -*- python -*- #BEGIN_LEGAL # #Copyright (c) 2017 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #END_LEGAL from __future__ import print_function import sys import os import math import xedhash class hashmul_t(xedhash.hash_fun_interface_t): """Implement multiplicative hashing.""" def __init__(self, table_size): # golden ratio phi is (1+sqrt(5))/2. From Knuth, volume 3, page 516 # 1/phi = (sqrt(5)-1)/2 (after some arithmetic) # We are using 1/phi * 2**n # where n is the number of bits in the data type (32) self.golden_ratio_recip2to32 = 2654435769 self.table_size = table_size # pow2 is True if the table is a power of 2. # ilog2_table_size is only valid if pow2 is True self.pow2, self.ilog2_table_size = self.power_of_2() def kind(self): return "mult" def power_of_2(self): ilog2_table_size = int(math.log(self.table_size,2)) if pow(2,ilog2_table_size) == self.table_size: return (True, ilog2_table_size) return (False, -1) def get_table_size(self): return self.table_size def __str__(self): return "h(x) = hashmul({})".format(self.table_size) def apply(self, k): """Apply the hash function to the key k""" #sys.stderr.write("Apply {} --> ".format(k)) q = self.golden_ratio_recip2to32 * k fraction = q & ((1<<32)-1) r = fraction * self.table_size v = r >> 32 #sys.stderr.write(" {}\n".format(v)) return v def apply_pow2(self, k): """Apply the hash function to the key k, for power of 2 table sizes""" q = self.golden_ratio_recip2to32 * k fraction = q & ((1<<32)-1) v = fraction >> (32-self.ilog2_table_size) return v def is_perfect(self, key_list): values = set() for k in key_list: #sys.stderr.write("Checking {}\n".format(k)) v = self.apply(k) if v in values: # collision - not perfect return False values.add(v) # no collisions in the output of the hash: perfect return True def need_hash_index_validation(self): """Need to validate that we landed on live bucket""" return True def add_key_validation(self, strings_dict): key_str = strings_dict['key_str'] hentry_str ='%s[%s]' % (strings_dict['table_name'], strings_dict['hidx_str']) return 'if(%s.key == %s)' % (hentry_str, key_str) def emit_cvar_decl(self): if self.pow2: return "xed_union64_t t" else: return "xed_union64_t t, u" def emit_cexpr(self, key_str="key"): """Emit a C expression for the hash function given a C variable key_str.""" if self.pow2: # power of 2 table size can replace the 2nd multiply with a shift c_hash_expr = """(t.u64 = {0} * {1}, t.s.lo32 >> (32-{2}))""".format( str(self.golden_ratio_recip2to32), key_str, self.ilog2_table_size) else: # the ULL cast on the constant is important to get 64b math. c_hash_expr = """(t.u64 = {0} * {1}, u.u64 = t.s.lo32 * {2}ULL, u.s.hi32)""".format( str(self.golden_ratio_recip2to32), key_str, str(self.table_size)) return c_hash_expr def find_perfect(keylist): n = len(keylist) for m in range(n,2*n): f = hashmul_t(n) if f.is_perfect(keylist): return f return None def test1(): f = hashmul_t(128) for k in range(0,128): v = f.apply(k) print("{} -> {}".format(k,v)) if f.is_perfect(range(0,128)): print("Hash function is perfect") else: print("Hash function has collisions") print(f.emit_cexpr()) return 0 def test2(): f = hashmul_t(9) inputs = [225,2273,737,2785,241,2289,753,2801] for k in inputs: v = f.apply(k) print("{} -> {}".format(k,v)) if f.is_perfect(inputs): print("Hash function is perfect") else: print("Hash function has collisions") print(f.emit_cexpr()) return 0 def test3(): f = hashmul_t(16) inputs = [225,2273,737,2785,241,2289,753,2801] for k in inputs: v1 = f.apply(k) v2 = f.apply_pow2(k) if v1 != v2: print("ERROR {} -> {} {}".format(k,v1,v2)) else: print("OK {} -> {} {}".format(k,v1,v2)) if f.is_perfect(inputs): print("Hash function is perfect") else: print("Hash function has collisions") print(f.emit_cexpr()) return 0 def test4(): f = hashmul_t(1) inputs = [68002] for k in inputs: v1 = f.apply(k) v2 = f.apply_pow2(k) if v1 != v2: print("ERROR {} -> {} {}".format(k,v1,v2)) else: print("OK {} -> {} {}".format(k,v1,v2)) if f.is_perfect(inputs): print("Hash function is perfect") else: print("Hash function has collisions") print(f.emit_cexpr()) return 0 def test(): for f in [test1, test2, test3, test4]: r = f() if r: print("FAIL: {}".format(f.__name__)) else: print("PASS: {}".format(f.__name__)) if __name__ == "__main__": r = test() sys.exit(r)