diff --git a/k2/csrc/hash.h b/k2/csrc/hash.h index 48459accb..ae651c6de 100644 --- a/k2/csrc/hash.h +++ b/k2/csrc/hash.h @@ -55,14 +55,43 @@ unsigned long long int __forceinline__ __host__ __device__ AtomicCAS( How class Hash works: - It can function as a map from key=uint32_t to value=uint32_t, or from - key=uint64_t to value=uint64_t where you choose NUM_KEY_BITS and - `key` must have only up to NUM_KEY_BITS set and `value` must have - only up to (64-NUM_KEY_BITS) set. You decide NUM_KEY_BITS when - you call Hash::Accessor<NUM_KEY_BITS>() - - You can store any (key,value) pair except the pair where all the bits of + key=uint64_t to value=uint64_t, but you cannot use all 64 bits in the + key and value because we compress both of them into a single 64-bit + integer. There are several different modes of using this hash, + depending which accessor objects you use. The modes are: + + - Use Accessor<NUM_KEY_BITS> with num_key_bits known at compile time; + the number of values bits will be 64 - NUM_KEY_BITS. + - Use GenericAccessor, which is like Accessor but the number of + key bits is not known at compile time; and they both must still + sum to 64. + - Use PackedAccessor, which allows you to have the number of key + plus value bits greater than 64; the rest of the bits are + implicit in groups of buckets (the number of buckets must + be >= 32 * 1 << (num_key_bits + num_value_bits - 64). + + - You must decide the number of key and value bits, and the number of + buckets, when you create the hash, but you can resize it (manually) + and when you resize it you can change the number of key and value bits. + + Some constraints: + - You can store any (key,value) pair allowed by the number of key and value + bits, except the pair where all the bits of both and key and value are set [that is used to mean "nothing here"] - - The number of buckets is a power of 2 provided by the user to the constructor; - currently no resizing is supported. + - The number of buckets must always be a power of 2. + - When deleting values from the hash you must delete them all at + once (necessary because there is no concept of a "tombstone". + + Some notes on usage: + + You use it by: constructing it, obtaining its Accessor with GetAccessor() + with appropriate template args depending on your chosen accessor type; and + inside kernels (or host code), calling functions Insert(), Find() or Delete() + of the Accessor object. Resizing is not automatic; it is the user's + responsibility to make sure the hash does not get too full (which could cause + assertion failures in kernels, and will be very slow). + + Some implementation notes: - When accessing hash[key], we use bucket_index == key % num_buckets, bucket_inc = 1 | (((key * 2) / num_buckets) ^ key). - If the bucket at `bucket_index` is occupied, we look in locations @@ -72,15 +101,7 @@ unsigned long long int __forceinline__ __host__ __device__ AtomicCAS( being odd ensures we eventually try all locations (of course for reasonable hash occupancy levels, we shouldn't ever have to try more than two or three). - - When deleting values from the hash you must delete them all at - once (necessary because there is no concept of a "tombstone". - You use it by: constructing it, obtaining its Accessor with - GetAccessor<NUM_KEY_BITS>(), and inside kernels (or host code), calling - functions Insert(), Find() or Delete() of the Accessor object. There is no - resizing; sizing it correctly is the caller's responsibility and if the hash - gets full the code will just loop forever (of course it will get extremely - slow before it reaches that point). */ class Hash { public: @@ -94,10 +115,14 @@ class Hash { @param [in] num_key_bits Number of bits in the key of the hash; must satisfy 0 < num_key_bits < 64, and keys used must be less than (1<<num_key_bits)-1. - @param [in] num_value_bits Number of bits in the value of the hash. - If not specified it defaults to 64 - num_key_bits; in future - we'll allow more bits than that, by making some bits of - the key implicit in the bucket index. + @param [in] num_value_bits Number of bits in the value of the hash; + if not specified, will be set to 64 - num_key_bits. There + are constraints on the num_value_bits, it interacts with + which accessor you use. For Accessor<> or GenericAccessor, + we require that num_key_bits + num_value_bits == 64. + For PackedAccessor we allow that num_key_bits + num_value_bits > 64, + but with the constraint that + (num_buckets >> (64 - num_key_bits - num_value_bits)) >= 32 */ Hash(ContextPtr c, int32_t num_buckets,