The URCU hash table API
cds_lfht_resize(),
but they will be carried out automatically when needed if
the CDS_LFHT_AUTO_RESIZE flag is passed to
cds_lfht_new(). There are API member functions to
carry out lookups (cds_lfht_lookup(),
cds_lfht_first(),
cds_lfht_next(), and
cds_lfht_next_duplicate()),
additions (cds_lfht_add(),
cds_lfht_add_unique(), and
cds_lfht_add_replace()), deletions
(cds_lfht_del()), and replacements
(cds_lfht_replace()).
There are also several functions that iterate over
elements in the hash table, namely
cds_lfht_for_each(),
cds_lfht_for_each_duplicate(),
cds_lfht_for_each_entry(), and
cds_lfht_for_each_entry_duplicate().
The individual API members are as follows:
struct cds_lfht *cds_lfht_new(unsigned long init_size, unsigned long min_nr_alloc_buckets, unsigned long max_nr_buckets, int flags, pthread_attr_t *attr)
The
cds_lfht_new()function allocates a new hash table and returns a pointer to it, orNULLon error. Its arguments are as follows:
init_size: Specifies the number of hash buckets to allocate initially, which must be a power of two.min_nr_alloc_buckets: Specifies the minimum number of hash buckets, which also must be a power of two.max_nr_buckets: Specifies the maximum number of hash buckets, which once more must be a power of two. Zero means “unlimited”.flags: Specifies hash-table options. A value of zero takes default values, otherwise the following flags may be specified, using bitwise OR (‘|’) to combine, if desired:
CDS_LFHT_AUTO_RESIZE: Automatically resize the hash table. Note that thecds_lfht_resize()function may be invoked to manually resize the table.CDS_LFHT_ACCOUNTING: Maintain counts of the number of nodes in the table. This flag is required to enabling shrinking a hash table. So CDS_LFHT_AUTO_RESIZE allows the hash table to grow, but “CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING” is required to also allow the hash table to shrink.attr: Optionalpthread_create()thread-creation attributes for the resize worker thread (orNULLto use the default attributes). One important use ofattroccurs in real-time applications, where it can be important to set the priorities of the resize worker threads so as to avoid starvation of these threads by an endless deluge ofcds_lfht_add()invocations. After all, such starvation could result in a too-small hash table having the lookup performance of a linked list. Note thatattris ignored unless or until the hash table is actually resized.Threads invoking
cds_lfht_new()are not required to be registered as RCU readers, which means that it is permissible to invokecds_lfht_new()very early, for example, before RCU has been initialized.
int cds_lfht_destroy(struct cds_lfht *ht, pthread_attr_t **attr)
The
cds_lfht_destroy()function deletes a hash table that was previously created usingcds_lfht_new(). If the deletion is successful, zero is returned. Thecds_lfht_destroy()function's arguments are as follows:
ht: A pointer to the hash table to destroy.attr: A pointer to a pointer in which to store theattrpointer that was passed to the corresponding call tocds_lfht_new. The purpose is to allow the caller to deallocate the storage. If the caller does not need to deallocate storage (for example, ifattrwas statically allocated),NULLmay be passed in.Threads invoking
cds_lfht_destroy()must be registered as RCU readers (usingrcu_register_thread()). It is illegal to invokecds_lfht_destroy()from within either an RCU read-side critical section or any function passed tocall_rcu().The hash table must be empty before this function is called, otherwise it will return failure. In addition, all other operations on the hash table must have ceased prior to the call to
cds_lfht_destroy().
void cds_lfht_resize(struct cds_lfht *ht, unsigned long new_size)
The
cds_lfht_resize()function initiates a resize operation. Its arguments are as follows:
ht: A pointer to the hash table to be resized.new_size: The desired size, which should be a power of two.Threads invoking
cds_lfht_resize()must be registered as RCU readers (usingrcu_register_thread()). Note that this function does not necessarily execute any memory barriers.
void cds_lfht_count_nodes(struct cds_lfht *ht, long *split_count_before, unsigned long *count, long *split_count_after)
The
cds_lfht_count_nodes()function counts the number of elements in the hash table. Note that nodes can be added to or removed from that hash table at any time, so that any count will necessarily be approximate unless the caller has prevented updates to the hash table for the duration.
ht: A pointer to the hash table.split_count_before: IfCDS_LFHT_ACCOUNTINGwas specified at hash-table creation, the sum of the resulting counts will be stored to the referenced variable prior to traversing the hash table. On the other hand, withoutCDS_LFHT_ACCOUNTING, the value stored will always be zero.count: The actual count of elements will be stored to the referenced variable. This count is obtained by iterating over the full hash table.split_count_after: IfCDS_LFHT_ACCOUNTINGwas specified at hash-table creation, the sum of the resulting counts will be stored to the referenced variable after traversing the hash table. Again, withoutCDS_LFHT_ACCOUNTING, the value stored will always be zero.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()).
Quick Quiz 1:
But if the caller to cds_lfht_count_nodes() needs to
be in an RCU read-side critical section, that sounds a lot like
this function really is counting the elements in the hash table
one at a time, which would be ridiculously slow on a large hash table.
Why not just keep a simple count of the number of elements in the
hash table?
You could just increment it when an element is added and decrement
it when that element is removed.
What could be simpler?
Answer
struct cds_lfht_node *cds_lfht_iter_get_node(struct cds_lfht_iter *iter)
This function extracts a pointer to the
cds_lfht_nodestructure from acds_lfht_iterstructure, such as that produced bycds_lfht_lookup(). The caller will normally need to applycaa_container_of()to map thecds_lfht_nodeto the enclosing data structure.
iter: A pointer to acds_lfht_iterstructure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). Note that this RCU read-side critical section must enclose the full code path from the initialcds_lfht_lookup()orcds_lfht_first()that initiated hash-table traversal resulting initerbeing produced.
void cds_lfht_lookup(struct cds_lfht *ht, unsigned long hash, cds_lfht_match_fct match, const void *key, struct cds_lfht_iter *iter)
The
cds_lfht_lookup()function looks up the specified element.
ht: A pointer to the hash table.hash: The hash of the desired element.match: The match function, which must be defined as follows:int match(struct cds_lfht_node *node, const void *key)This function must return non-zero ifkeymatches that associated withnodeand zero otherwise.key: The key of the desired element. Note that prior to callingcds_lfht_lookup(), the caller must have computedhashfrom the object referenced bykey.iter: A pointer to a caller-suppliedcds_lfht_iterstructure into whichcds_lfht_lookup()places the results of the lookup. Thecds_lfht_iter_get_node()function may be used to extract the resulting node fromiter.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). Note that thecds_lfht_lookup()function takes on the role ofrcu_dereference(), allowing the element's fields to be accessed normally.
void cds_lfht_first(struct cds_lfht *ht, struct cds_lfht_iter *iter)
The
cds_lfht_first()function looks up the first element in the hash table. This function may be used in place ofcds_lfht_lookup()in order to begin a full scan of all elements in the hash table.
ht: A pointer to the hash table.iter: A pointer to a caller-suppliedcds_lfht_iterstructure into whichcds_lfht_first()places the results of the lookup. Thecds_lfht_iter_get_node()function may be used to extract the resulting node fromiter.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). Note that thecds_lfht_first()function takes on the role ofrcu_dereference(), allowing the element's fields to be read and written normally.
void cds_lfht_next(struct cds_lfht *ht, struct cds_lfht_iter *iter)
Advances the specified iterator to the next hash element, even if that element is in some later hash bucket.
ht: A pointer to the hash table.iter: A pointer to a caller-supplied iterator marking the current element in the hash table. Upon return, this will be updated to reference the next element, or theNULLelement if there is no next element. Either way, thecds_lfht_iter_get_node()function may be used to extract the resulting node fromiter.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). Note that thecds_lfht_next()function takes on the role ofrcu_dereference(), allowing the element's fields to be read and written normally.
void cds_lfht_next_duplicate(struct cds_lfht *ht, cds_lfht_match_fct match, const void *key, struct cds_lfht_iter *iter)
Advances the specified iterator to the next hash element, but only if the next element matches the specified key.
ht: A pointer to the hash table.match: The match function, which must be defined as described incds_lfht_lookup().key: The key of the desired element.iter: A pointer to a caller-supplied iterator marking the current element in the hash table. Upon return, this will be updated to reference the next element, or theNULLelement if there is no next element. Either way, thecds_lfht_iter_get_node()function may be used to extract the resulting node fromiter.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). Note that thecds_lfht_next_duplicate()function takes on the role ofrcu_dereference(), allowing the element's fields to be read and written normally.
Quick Quiz 2:
Why doesn't the caller supply a hash to cds_lfht_next_duplicate()?
Wouldn't that speed up the rejection of the mismatches?
Answer
int cds_lfht_is_node_deleted(struct cds_lfht_node *node)
Checks to see whether the specified node has been deleted, returning non-zero if so.
node: A pointer to the node to be checked for deletion.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). Note that this RCU read-side critical section must enclose the full code path from the initialcds_lfht_lookup()orcds_lfht_first()that initiated this hash-table traversal.
cds_lfht_for_each(ht, iter, node)
Iterates over all entries in the hash table. This macro expands to a
forstatement, so it must be immediately followed by either a statement or a statement block.
ht: A pointer to the hash table.iter: A pointer to a caller-suppliedcds_lfht_iterstructure thatcds_lfht_for_each()uses to control the traversal.node: Acds_lfht_nodepointer that is iterated over all nodes in the hash table. Usecaa_container_of()to map the node to the enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This RCU read-side critical section must enclose the entire loop: It is illegal to momentarily exit the RCU read-side critical section in the body of the loop.
Quick Quiz 3:
Why bother with the cds_lfht_iter structure?
Why not just iterate over the cds_lfht_node
structures making up the hash table itself?
Answer
cds_lfht_for_each_duplicate(ht, hash, match, key, iter, node)
Iterates over all entries in the hash table having the specified key. This macro expands to a
forstatement, so it must be immediately followed by either a statement or a statement block.
ht: A pointer to the hash table.hash: The hash of the key value.match: The match function, which must be defined as described incds_lfht_lookup().key: The desired key.iter: A pointer to a caller-suppliedcds_lfht_iterstructure thatcds_lfht_for_each()uses to control the traversal.node: Acds_lfht_nodepointer that is iterated over all nodes in the hash table. Usecaa_container_of()to map the node to the enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This RCU read-side critical section must enclose the entire loop: It is illegal to momentarily exit the RCU read-side critical section in the body of the loop.
cds_lfht_for_each_entry(ht, iter, pos, member)
Iterates over the enclosing data structure of each entry in the hash table. This macro expands to a
forstatement, so it must be immediately followed by either a statement or a statement block.
ht: A pointer to the hash table.iter: A pointer to a caller-suppliedcds_lfht_iterstructure thatcds_lfht_for_each()uses to control the traversal.pos: A pointer of the type of the enclosing data structure.member: The name of thecds_lfht_nodefield within the enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This RCU read-side critical section must enclose the entire loop: It is illegal to momentarily exit the RCU read-side critical section in the body of the loop.
cds_lfht_for_each_entry_duplicate(ht, hash, match, key, iter, pos, member)
Iterates over the enclosing data structure of each entry in the hash table matching the specified key. This macro expands to a
forstatement, so it must be immediately followed by either a statement or a statement block.
ht: A pointer to the hash table.hash: The hash of the key value.match: The match function, which must be defined as described incds_lfht_lookup().key: The desired key.iter: A pointer to a caller-suppliedcds_lfht_iterstructure thatcds_lfht_for_each()uses to control the traversal.pos: A pointer of the type of the enclosing data structure.member: The name of thecds_lfht_nodefield within the enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This RCU read-side critical section must enclose the entire loop: It is illegal to momentarily exit the RCU read-side critical section in the body of the loop.
void cds_lfht_add(struct cds_lfht *ht, unsigned long hash, struct cds_lfht_node *node)
Adds the specified node to the hash table. This function is the only way to add duplicate keys.
ht: A pointer to the hash table.hash: The hash of the key value.node: A pointer to thecds_lfht_nodefield of the enclosing data structure to be added.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This function issues a full memory barrier before and after its atomic commit.
struct cds_lfht_node *cds_lfht_add_unique(struct cds_lfht *ht, unsigned long hash, cds_lfht_match_fct match, const void *key, struct cds_lfht_node *node)
Adds the specified node to the hash table, but only if there is not already a node with the specified key. Returns the node added, if successful, otherwise returns the node from the hash table that has the specified key.
ht: A pointer to the hash table.hash: The hash of the key value.match: The match function, which must be defined as described incds_lfht_lookup().key: The desired key.node: A pointer to thecds_lfht_nodefield of the replacement enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()).Upon success, this function issues a full memory barrier before and after its atomic commit. Upon failure, this function takes on the role of
rcu_dereference(), allowing the conflicting element's fields to be read and written normally.
struct cds_lfht_node *cds_lfht_add_replace(struct cds_lfht *ht, unsigned long hash, cds_lfht_match_fct match, const void *key, struct cds_lfht_node *node)
Replaces the node with the specified key, or, if there is no such node, adds one. Returns the node replaced, if such a node existed, otherwise returns
NULL.
ht: A pointer to the hash table.hash: The hash of the key value.match: The match function, which must be defined as described incds_lfht_lookup().key: The desired key.node: A pointer to thecds_lfht_nodefield of the replacement enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This function issues a full memory barrier before and after its atomic commit.If a
cds_lfht_add_replace()successfully replaces an existing node, then any concurrentcds_lfht_lookup()using that same key is guaranteed not to fail, even momentarily.The caller must wait a full grace period (e.g., by calling
synchronize_rcu()between replacement and freeing the old node that was replaced.
int cds_lfht_replace(struct cds_lfht *ht, struct cds_lfht_iter *old_iter, unsigned long hash, cds_lfht_match_fct match, const void *key, struct cds_lfht_node *new_node)
Replaces the node with the specified key, or, if there is no such node, returns failure (
-ENOENT).
ht: A pointer to the hash table.old_iter: A pointer to a caller-supplied iterator marking the current element in the hash table.hash: The hash of the key value.match: The match function, which must be defined as described incds_lfht_lookup().key: The desired key.new_node: A pointer to thecds_lfht_nodefield of the replacement enclosing data structure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()).Upon success, this function issues a full memory barrier before and after its atomic commit. Upon failure, this function provides no memory-ordering semantics.
If a
cds_lfht_replace()successfully replaces an existing node, then any concurrentcds_lfht_lookup()using that same key is guaranteed not to fail, even momentarily.The caller must wait a full grace period (e.g., by calling
synchronize_rcu()between replacement and freeing the old node that was replaced.
int cds_lfht_del(struct cds_lfht *ht, struct cds_lfht_node *node)
Deletes the specified node from the hash table, returning zero on success or a negative error code upon failure.
ht: A pointer to the hash table.node: A pointer to thecds_lfht_nodefield of the enclosing data structure to be deleted. ANULLnodewill result in failure.The caller must be in an RCU read-side critical section, and therefore must have been registered as an RCU reader (using
rcu_register_thread()). This RCU read-side critical section must enclose bothcds_lfht_del()invocation as well as whatever chain of function calls looked upnode.Upon success, this function issues a full memory barrier before and after its atomic commit. Upon failure, this function provides no memory-ordering semantics.
The caller must wait a full grace period (e.g., by calling
synchronize_rcu()between deletion and freeing the deleted node.
Answers to Quick Quizzes
Quick Quiz 1:
But if the caller to cds_lfht_count_nodes() needs to
be in an RCU read-side critical section, that sounds a lot like
this function really is counting the elements in the hash table
one at a time, which would be ridiculously slow on a large hash table.
Why not just keep a simple count of the number of elements in the
hash table?
You could just increment it when an element is added and decrement
it when that element is removed.
What could be simpler?
Answer: That would indeed be simple, but it would also ruin the performance and scalability of element addition and removal. There are of course special purpose counters that perform and scale well, but these tend to be rather specialized. Therefore, if fast counting is important, the user of the hash table should enlist the aid of whatever parallel counter is appropriate. Several such counters may be found in the counting chapter of Is Parallel Programming Hard, And, If So, What Can You Do About It?.
Quick Quiz 2:
Why doesn't the caller supply a hash to cds_lfht_next_duplicate()?
Wouldn't that speed up the rejection of the mismatches?
Answer:
Because cds_lfht_next_duplicate() continues a traversal from
a node having the desired hash value, cds_lfht_next_duplicate()
can load the key (already bit-reversed) from that previous node.
This raises the question of why cds_lfht_next_duplicate()
can't also access the key from that same previous node.
The answer is that the hash table doesn't know where the key is.
Instead, it is passed a caller-provided match() function that
knows where the key is in the caller-defined portion of the node.
This approach allows the caller to use an arbitrarily defined key,
but also requires that cds_lfht_next_duplicate() be passed
the key.
Quick Quiz 3:
Why bother with the cds_lfht_iter structure?
Why not just iterate over the cds_lfht_node
structures making up the hash table itself?
Answer:
The separate cds_lfht_iter structure is required in order to
correctly handle cds_lfht_replace() and
cds_lfht_add_replace()
invocations executed concurrently with lookups and traversals.
The
cds_lfht_replace()
and cds_lfht_add_replace() operations provide their
uniqueness guarantees by adding the replacement node immediately
after the node being replaced.
The trick is that a given node can be marked as deleted without actually
removing it from the hash table by setting a bit in
bottom bits of the pointer to the deleted node's next node.
This means that the cds_lfht_replace() and
cds_lfht_add_replace() operations can set the
“deleted” bit in the pointer to the replacement node
that is stored in the node being replaced, thus adding the replacement node
and deleting the replaced node atomically with a single store.
Of course, nodes marked deleted must eventually be removed from the hash table, but this removal can be done in a lazy fashion. This process is (fancifully) illustrated in the following diagram:
Time advances from left to right through five states.
In the first state, we have elements A, B, and C.
An invocation of cds_lfht_replace() replaces element B
with B', marking the pointer out of element B as shown in the
second state.
Note that this replacement of element B with B' is carried
out with a single store to element B's ->next
pointer, allowing the replacement to appear atomic to readers.
At some later time, the now-obsolete element B will be removed
from the list, as shown in the third state, after which it can no
longer be accessed by new readers (hence the change in color from
red to yellow).
A later invocation of synchronize_rcu() waits for all
pre-existing readers to complete, so that there are no longer any
readers accessing the old element B (hence the change in color
from yellow to green), as shown in the fourth state.
At this point, it is safe to free element B, as shown in the
fifth and final state.
On the lookup and traversal side, the problem is that we cannot
fetch a given pointer twice, as the pointer might change in the
meantime.
In addition, before we return a pointer to a given object, we
must check the pointer to the next object in order to check to see
if it has been marked deleted.
But we will need to refer to this same pointer on the
next pass through the loop.
Therefore, we need to track two pointers across the body of the loop,
and the cds_lfht_iter structure is where these pointers
are stored.
