mirror of
https://github.com/torvalds/linux.git
synced 2024-11-13 23:51:39 +00:00
513789b7fb
Reported-by: kernel test robot <lkp@intel.com> Closes: https://lore.kernel.org/oe-kbuild-all/202407141607.M3E2XQ0Z-lkp@intel.com/ Signed-off-by: Matthew Sakai <msakai@redhat.com> Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
708 lines
24 KiB
C
708 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2023 Red Hat
|
|
*/
|
|
|
|
/**
|
|
* DOC:
|
|
*
|
|
* Hash table implementation of a map from integers to pointers, implemented using the Hopscotch
|
|
* Hashing algorithm by Herlihy, Shavit, and Tzafrir (see
|
|
* http://en.wikipedia.org/wiki/Hopscotch_hashing). This implementation does not contain any of the
|
|
* locking/concurrency features of the algorithm, just the collision resolution scheme.
|
|
*
|
|
* Hopscotch Hashing is based on hashing with open addressing and linear probing. All the entries
|
|
* are stored in a fixed array of buckets, with no dynamic allocation for collisions. Unlike linear
|
|
* probing, all the entries that hash to a given bucket are stored within a fixed neighborhood
|
|
* starting at that bucket. Chaining is effectively represented as a bit vector relative to each
|
|
* bucket instead of as pointers or explicit offsets.
|
|
*
|
|
* When an empty bucket cannot be found within a given neighborhood, subsequent neighborhoods are
|
|
* searched, and one or more entries will "hop" into those neighborhoods. When this process works,
|
|
* an empty bucket will move into the desired neighborhood, allowing the entry to be added. When
|
|
* that process fails (typically when the buckets are around 90% full), the table must be resized
|
|
* and the all entries rehashed and added to the expanded table.
|
|
*
|
|
* Unlike linear probing, the number of buckets that must be searched in the worst case has a fixed
|
|
* upper bound (the size of the neighborhood). Those entries occupy a small number of memory cache
|
|
* lines, leading to improved use of the cache (fewer misses on both successful and unsuccessful
|
|
* searches). Hopscotch hashing outperforms linear probing at much higher load factors, so even
|
|
* with the increased memory burden for maintaining the hop vectors, less memory is needed to
|
|
* achieve that performance. Hopscotch is also immune to "contamination" from deleting entries
|
|
* since entries are genuinely removed instead of being replaced by a placeholder.
|
|
*
|
|
* The published description of the algorithm used a bit vector, but the paper alludes to an offset
|
|
* scheme which is used by this implementation. Since the entries in the neighborhood are within N
|
|
* entries of the hash bucket at the start of the neighborhood, a pair of small offset fields each
|
|
* log2(N) bits wide is all that's needed to maintain the hops as a linked list. In order to encode
|
|
* "no next hop" (i.e. NULL) as the natural initial value of zero, the offsets are biased by one
|
|
* (i.e. 0 => NULL, 1 => offset=0, 2 => offset=1, etc.) We can represent neighborhoods of up to 255
|
|
* entries with just 8+8=16 bits per entry. The hop list is sorted by hop offset so the first entry
|
|
* in the list is always the bucket closest to the start of the neighborhood.
|
|
*
|
|
* While individual accesses tend to be very fast, the table resize operations are very, very
|
|
* expensive. If an upper bound on the latency of adding an entry to the table is needed, we either
|
|
* need to ensure the table is pre-sized to be large enough so no resize is ever needed, or we'll
|
|
* need to develop an approach to incrementally resize the table.
|
|
*/
|
|
|
|
#include "int-map.h"
|
|
|
|
#include <linux/minmax.h>
|
|
|
|
#include "errors.h"
|
|
#include "logger.h"
|
|
#include "memory-alloc.h"
|
|
#include "numeric.h"
|
|
#include "permassert.h"
|
|
|
|
#define DEFAULT_CAPACITY 16 /* the number of neighborhoods in a new table */
|
|
#define NEIGHBORHOOD 255 /* the number of buckets in each neighborhood */
|
|
#define MAX_PROBES 1024 /* limit on the number of probes for a free bucket */
|
|
#define NULL_HOP_OFFSET 0 /* the hop offset value terminating the hop list */
|
|
#define DEFAULT_LOAD 75 /* a compromise between memory use and performance */
|
|
|
|
/**
|
|
* struct bucket - hash bucket
|
|
*
|
|
* Buckets are packed together to reduce memory usage and improve cache efficiency. It would be
|
|
* tempting to encode the hop offsets separately and maintain alignment of key/value pairs, but
|
|
* it's crucial to keep the hop fields near the buckets that they use them so they'll tend to share
|
|
* cache lines.
|
|
*/
|
|
struct __packed bucket {
|
|
/**
|
|
* @first_hop: The biased offset of the first entry in the hop list of the neighborhood
|
|
* that hashes to this bucket.
|
|
*/
|
|
u8 first_hop;
|
|
/** @next_hop: The biased offset of the next bucket in the hop list. */
|
|
u8 next_hop;
|
|
/** @key: The key stored in this bucket. */
|
|
u64 key;
|
|
/** @value: The value stored in this bucket (NULL if empty). */
|
|
void *value;
|
|
};
|
|
|
|
/**
|
|
* struct int_map - The concrete definition of the opaque int_map type.
|
|
*
|
|
* To avoid having to wrap the neighborhoods of the last entries back around to the start of the
|
|
* bucket array, we allocate a few more buckets at the end of the array instead, which is why
|
|
* capacity and bucket_count are different.
|
|
*/
|
|
struct int_map {
|
|
/** @size: The number of entries stored in the map. */
|
|
size_t size;
|
|
/** @capacity: The number of neighborhoods in the map. */
|
|
size_t capacity;
|
|
/** @bucket_count: The number of buckets in the bucket array. */
|
|
size_t bucket_count;
|
|
/** @buckets: The array of hash buckets. */
|
|
struct bucket *buckets;
|
|
};
|
|
|
|
/**
|
|
* mix() - The Google CityHash 16-byte hash mixing function.
|
|
* @input1: The first input value.
|
|
* @input2: The second input value.
|
|
*
|
|
* Return: A hash of the two inputs.
|
|
*/
|
|
static u64 mix(u64 input1, u64 input2)
|
|
{
|
|
static const u64 CITY_MULTIPLIER = 0x9ddfea08eb382d69ULL;
|
|
u64 hash = (input1 ^ input2);
|
|
|
|
hash *= CITY_MULTIPLIER;
|
|
hash ^= (hash >> 47);
|
|
hash ^= input2;
|
|
hash *= CITY_MULTIPLIER;
|
|
hash ^= (hash >> 47);
|
|
hash *= CITY_MULTIPLIER;
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
* hash_key() - Calculate a 64-bit non-cryptographic hash value for the provided 64-bit integer
|
|
* key.
|
|
* @key: The mapping key.
|
|
*
|
|
* The implementation is based on Google's CityHash, only handling the specific case of an 8-byte
|
|
* input.
|
|
*
|
|
* Return: The hash of the mapping key.
|
|
*/
|
|
static u64 hash_key(u64 key)
|
|
{
|
|
/*
|
|
* Aliasing restrictions forbid us from casting pointer types, so use a union to convert a
|
|
* single u64 to two u32 values.
|
|
*/
|
|
union {
|
|
u64 u64;
|
|
u32 u32[2];
|
|
} pun = {.u64 = key};
|
|
|
|
return mix(sizeof(key) + (((u64) pun.u32[0]) << 3), pun.u32[1]);
|
|
}
|
|
|
|
/**
|
|
* allocate_buckets() - Initialize an int_map.
|
|
* @map: The map to initialize.
|
|
* @capacity: The initial capacity of the map.
|
|
*
|
|
* Return: VDO_SUCCESS or an error code.
|
|
*/
|
|
static int allocate_buckets(struct int_map *map, size_t capacity)
|
|
{
|
|
map->size = 0;
|
|
map->capacity = capacity;
|
|
|
|
/*
|
|
* Allocate NEIGHBORHOOD - 1 extra buckets so the last bucket can have a full neighborhood
|
|
* without have to wrap back around to element zero.
|
|
*/
|
|
map->bucket_count = capacity + (NEIGHBORHOOD - 1);
|
|
return vdo_allocate(map->bucket_count, struct bucket,
|
|
"struct int_map buckets", &map->buckets);
|
|
}
|
|
|
|
/**
|
|
* vdo_int_map_create() - Allocate and initialize an int_map.
|
|
* @initial_capacity: The number of entries the map should initially be capable of holding (zero
|
|
* tells the map to use its own small default).
|
|
* @map_ptr: Output, a pointer to hold the new int_map.
|
|
*
|
|
* Return: VDO_SUCCESS or an error code.
|
|
*/
|
|
int vdo_int_map_create(size_t initial_capacity, struct int_map **map_ptr)
|
|
{
|
|
struct int_map *map;
|
|
int result;
|
|
size_t capacity;
|
|
|
|
result = vdo_allocate(1, struct int_map, "struct int_map", &map);
|
|
if (result != VDO_SUCCESS)
|
|
return result;
|
|
|
|
/* Use the default capacity if the caller did not specify one. */
|
|
capacity = (initial_capacity > 0) ? initial_capacity : DEFAULT_CAPACITY;
|
|
|
|
/*
|
|
* Scale up the capacity by the specified initial load factor. (i.e to hold 1000 entries at
|
|
* 80% load we need a capacity of 1250)
|
|
*/
|
|
capacity = capacity * 100 / DEFAULT_LOAD;
|
|
|
|
result = allocate_buckets(map, capacity);
|
|
if (result != VDO_SUCCESS) {
|
|
vdo_int_map_free(vdo_forget(map));
|
|
return result;
|
|
}
|
|
|
|
*map_ptr = map;
|
|
return VDO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* vdo_int_map_free() - Free an int_map.
|
|
* @map: The int_map to free.
|
|
*
|
|
* NOTE: The map does not own the pointer values stored in the map and they are not freed by this
|
|
* call.
|
|
*/
|
|
void vdo_int_map_free(struct int_map *map)
|
|
{
|
|
if (map == NULL)
|
|
return;
|
|
|
|
vdo_free(vdo_forget(map->buckets));
|
|
vdo_free(vdo_forget(map));
|
|
}
|
|
|
|
/**
|
|
* vdo_int_map_size() - Get the number of entries stored in an int_map.
|
|
* @map: The int_map to query.
|
|
*
|
|
* Return: The number of entries in the map.
|
|
*/
|
|
size_t vdo_int_map_size(const struct int_map *map)
|
|
{
|
|
return map->size;
|
|
}
|
|
|
|
/**
|
|
* dereference_hop() - Convert a biased hop offset within a neighborhood to a pointer to the bucket
|
|
* it references.
|
|
* @neighborhood: The first bucket in the neighborhood.
|
|
* @hop_offset: The biased hop offset to the desired bucket.
|
|
*
|
|
* Return: NULL if hop_offset is zero, otherwise a pointer to the bucket in the neighborhood at
|
|
* hop_offset - 1.
|
|
*/
|
|
static struct bucket *dereference_hop(struct bucket *neighborhood, unsigned int hop_offset)
|
|
{
|
|
BUILD_BUG_ON(NULL_HOP_OFFSET != 0);
|
|
if (hop_offset == NULL_HOP_OFFSET)
|
|
return NULL;
|
|
|
|
return &neighborhood[hop_offset - 1];
|
|
}
|
|
|
|
/**
|
|
* insert_in_hop_list() - Add a bucket into the hop list for the neighborhood.
|
|
* @neighborhood: The first bucket in the neighborhood.
|
|
* @new_bucket: The bucket to add to the hop list.
|
|
*
|
|
* The bucket is inserted it into the list so the hop list remains sorted by hop offset.
|
|
*/
|
|
static void insert_in_hop_list(struct bucket *neighborhood, struct bucket *new_bucket)
|
|
{
|
|
/* Zero indicates a NULL hop offset, so bias the hop offset by one. */
|
|
int hop_offset = 1 + (new_bucket - neighborhood);
|
|
|
|
/* Handle the special case of adding a bucket at the start of the list. */
|
|
int next_hop = neighborhood->first_hop;
|
|
|
|
if ((next_hop == NULL_HOP_OFFSET) || (next_hop > hop_offset)) {
|
|
new_bucket->next_hop = next_hop;
|
|
neighborhood->first_hop = hop_offset;
|
|
return;
|
|
}
|
|
|
|
/* Search the hop list for the insertion point that maintains the sort order. */
|
|
for (;;) {
|
|
struct bucket *bucket = dereference_hop(neighborhood, next_hop);
|
|
|
|
next_hop = bucket->next_hop;
|
|
|
|
if ((next_hop == NULL_HOP_OFFSET) || (next_hop > hop_offset)) {
|
|
new_bucket->next_hop = next_hop;
|
|
bucket->next_hop = hop_offset;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* select_bucket() - Select and return the hash bucket for a given search key.
|
|
* @map: The map to search.
|
|
* @key: The mapping key.
|
|
*/
|
|
static struct bucket *select_bucket(const struct int_map *map, u64 key)
|
|
{
|
|
/*
|
|
* Calculate a good hash value for the provided key. We want exactly 32 bits, so mask the
|
|
* result.
|
|
*/
|
|
u64 hash = hash_key(key) & 0xFFFFFFFF;
|
|
|
|
/*
|
|
* Scale the 32-bit hash to a bucket index by treating it as a binary fraction and
|
|
* multiplying that by the capacity. If the hash is uniformly distributed over [0 ..
|
|
* 2^32-1], then (hash * capacity / 2^32) should be uniformly distributed over [0 ..
|
|
* capacity-1]. The multiply and shift is much faster than a divide (modulus) on X86 CPUs.
|
|
*/
|
|
return &map->buckets[(hash * map->capacity) >> 32];
|
|
}
|
|
|
|
/**
|
|
* search_hop_list() - Search the hop list associated with given hash bucket for a given search
|
|
* key.
|
|
* @map: The map being searched.
|
|
* @bucket: The map bucket to search for the key.
|
|
* @key: The mapping key.
|
|
* @previous_ptr: Output. if not NULL, a pointer in which to store the bucket in the list preceding
|
|
* the one that had the matching key
|
|
*
|
|
* If the key is found, returns a pointer to the entry (bucket or collision), otherwise returns
|
|
* NULL.
|
|
*
|
|
* Return: An entry that matches the key, or NULL if not found.
|
|
*/
|
|
static struct bucket *search_hop_list(struct int_map *map __always_unused,
|
|
struct bucket *bucket,
|
|
u64 key,
|
|
struct bucket **previous_ptr)
|
|
{
|
|
struct bucket *previous = NULL;
|
|
unsigned int next_hop = bucket->first_hop;
|
|
|
|
while (next_hop != NULL_HOP_OFFSET) {
|
|
/*
|
|
* Check the neighboring bucket indexed by the offset for the
|
|
* desired key.
|
|
*/
|
|
struct bucket *entry = dereference_hop(bucket, next_hop);
|
|
|
|
if ((key == entry->key) && (entry->value != NULL)) {
|
|
if (previous_ptr != NULL)
|
|
*previous_ptr = previous;
|
|
return entry;
|
|
}
|
|
next_hop = entry->next_hop;
|
|
previous = entry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* vdo_int_map_get() - Retrieve the value associated with a given key from the int_map.
|
|
* @map: The int_map to query.
|
|
* @key: The key to look up.
|
|
*
|
|
* Return: The value associated with the given key, or NULL if the key is not mapped to any value.
|
|
*/
|
|
void *vdo_int_map_get(struct int_map *map, u64 key)
|
|
{
|
|
struct bucket *match = search_hop_list(map, select_bucket(map, key), key, NULL);
|
|
|
|
return ((match != NULL) ? match->value : NULL);
|
|
}
|
|
|
|
/**
|
|
* resize_buckets() - Increase the number of hash buckets.
|
|
* @map: The map to resize.
|
|
*
|
|
* Resizes and rehashes all the existing entries, storing them in the new buckets.
|
|
*
|
|
* Return: VDO_SUCCESS or an error code.
|
|
*/
|
|
static int resize_buckets(struct int_map *map)
|
|
{
|
|
int result;
|
|
size_t i;
|
|
|
|
/* Copy the top-level map data to the stack. */
|
|
struct int_map old_map = *map;
|
|
|
|
/* Re-initialize the map to be empty and 50% larger. */
|
|
size_t new_capacity = map->capacity / 2 * 3;
|
|
|
|
vdo_log_info("%s: attempting resize from %zu to %zu, current size=%zu",
|
|
__func__, map->capacity, new_capacity, map->size);
|
|
result = allocate_buckets(map, new_capacity);
|
|
if (result != VDO_SUCCESS) {
|
|
*map = old_map;
|
|
return result;
|
|
}
|
|
|
|
/* Populate the new hash table from the entries in the old bucket array. */
|
|
for (i = 0; i < old_map.bucket_count; i++) {
|
|
struct bucket *entry = &old_map.buckets[i];
|
|
|
|
if (entry->value == NULL)
|
|
continue;
|
|
|
|
result = vdo_int_map_put(map, entry->key, entry->value, true, NULL);
|
|
if (result != VDO_SUCCESS) {
|
|
/* Destroy the new partial map and restore the map from the stack. */
|
|
vdo_free(vdo_forget(map->buckets));
|
|
*map = old_map;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/* Destroy the old bucket array. */
|
|
vdo_free(vdo_forget(old_map.buckets));
|
|
return VDO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* find_empty_bucket() - Probe the bucket array starting at the given bucket for the next empty
|
|
* bucket, returning a pointer to it.
|
|
* @map: The map containing the buckets to search.
|
|
* @bucket: The bucket at which to start probing.
|
|
* @max_probes: The maximum number of buckets to search.
|
|
*
|
|
* NULL will be returned if the search reaches the end of the bucket array or if the number of
|
|
* linear probes exceeds a specified limit.
|
|
*
|
|
* Return: The next empty bucket, or NULL if the search failed.
|
|
*/
|
|
static struct bucket *
|
|
find_empty_bucket(struct int_map *map, struct bucket *bucket, unsigned int max_probes)
|
|
{
|
|
/*
|
|
* Limit the search to either the nearer of the end of the bucket array or a fixed distance
|
|
* beyond the initial bucket.
|
|
*/
|
|
ptrdiff_t remaining = &map->buckets[map->bucket_count] - bucket;
|
|
struct bucket *sentinel = &bucket[min_t(ptrdiff_t, remaining, max_probes)];
|
|
struct bucket *entry;
|
|
|
|
for (entry = bucket; entry < sentinel; entry++) {
|
|
if (entry->value == NULL)
|
|
return entry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* move_empty_bucket() - Move an empty bucket closer to the start of the bucket array.
|
|
* @map: The map containing the bucket.
|
|
* @hole: The empty bucket to fill with an entry that precedes it in one of its enclosing
|
|
* neighborhoods.
|
|
*
|
|
* This searches the neighborhoods that contain the empty bucket for a non-empty bucket closer to
|
|
* the start of the array. If such a bucket is found, this swaps the two buckets by moving the
|
|
* entry to the empty bucket.
|
|
*
|
|
* Return: The bucket that was vacated by moving its entry to the provided hole, or NULL if no
|
|
* entry could be moved.
|
|
*/
|
|
static struct bucket *move_empty_bucket(struct int_map *map __always_unused,
|
|
struct bucket *hole)
|
|
{
|
|
/*
|
|
* Examine every neighborhood that the empty bucket is part of, starting with the one in
|
|
* which it is the last bucket. No boundary check is needed for the negative array
|
|
* arithmetic since this function is only called when hole is at least NEIGHBORHOOD cells
|
|
* deeper into the array than a valid bucket.
|
|
*/
|
|
struct bucket *bucket;
|
|
|
|
for (bucket = &hole[1 - NEIGHBORHOOD]; bucket < hole; bucket++) {
|
|
/*
|
|
* Find the entry that is nearest to the bucket, which means it will be nearest to
|
|
* the hash bucket whose neighborhood is full.
|
|
*/
|
|
struct bucket *new_hole = dereference_hop(bucket, bucket->first_hop);
|
|
|
|
if (new_hole == NULL) {
|
|
/*
|
|
* There are no buckets in this neighborhood that are in use by this one
|
|
* (they must all be owned by overlapping neighborhoods).
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Skip this bucket if its first entry is actually further away than the hole that
|
|
* we're already trying to fill.
|
|
*/
|
|
if (hole < new_hole)
|
|
continue;
|
|
|
|
/*
|
|
* We've found an entry in this neighborhood that we can "hop" further away, moving
|
|
* the hole closer to the hash bucket, if not all the way into its neighborhood.
|
|
*/
|
|
|
|
/*
|
|
* The entry that will be the new hole is the first bucket in the list, so setting
|
|
* first_hop is all that's needed remove it from the list.
|
|
*/
|
|
bucket->first_hop = new_hole->next_hop;
|
|
new_hole->next_hop = NULL_HOP_OFFSET;
|
|
|
|
/* Move the entry into the original hole. */
|
|
hole->key = new_hole->key;
|
|
hole->value = new_hole->value;
|
|
new_hole->value = NULL;
|
|
|
|
/* Insert the filled hole into the hop list for the neighborhood. */
|
|
insert_in_hop_list(bucket, hole);
|
|
return new_hole;
|
|
}
|
|
|
|
/* We couldn't find an entry to relocate to the hole. */
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* update_mapping() - Find and update any existing mapping for a given key, returning the value
|
|
* associated with the key in the provided pointer.
|
|
* @map: The int_map to attempt to modify.
|
|
* @neighborhood: The first bucket in the neighborhood that would contain the search key
|
|
* @key: The key with which to associate the new value.
|
|
* @new_value: The value to be associated with the key.
|
|
* @update: Whether to overwrite an existing value.
|
|
* @old_value_ptr: a pointer in which to store the old value (unmodified if no mapping was found)
|
|
*
|
|
* Return: true if the map contains a mapping for the key, false if it does not.
|
|
*/
|
|
static bool update_mapping(struct int_map *map, struct bucket *neighborhood,
|
|
u64 key, void *new_value, bool update, void **old_value_ptr)
|
|
{
|
|
struct bucket *bucket = search_hop_list(map, neighborhood, key, NULL);
|
|
|
|
if (bucket == NULL) {
|
|
/* There is no bucket containing the key in the neighborhood. */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Return the value of the current mapping (if desired) and update the mapping with the new
|
|
* value (if desired).
|
|
*/
|
|
if (old_value_ptr != NULL)
|
|
*old_value_ptr = bucket->value;
|
|
if (update)
|
|
bucket->value = new_value;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* find_or_make_vacancy() - Find an empty bucket.
|
|
* @map: The int_map to search or modify.
|
|
* @neighborhood: The first bucket in the neighborhood in which an empty bucket is needed for a new
|
|
* mapping.
|
|
*
|
|
* Find an empty bucket in a specified neighborhood for a new mapping or attempt to re-arrange
|
|
* mappings so there is such a bucket. This operation may fail (returning NULL) if an empty bucket
|
|
* is not available or could not be relocated to the neighborhood.
|
|
*
|
|
* Return: a pointer to an empty bucket in the desired neighborhood, or NULL if a vacancy could not
|
|
* be found or arranged.
|
|
*/
|
|
static struct bucket *find_or_make_vacancy(struct int_map *map,
|
|
struct bucket *neighborhood)
|
|
{
|
|
/* Probe within and beyond the neighborhood for the first empty bucket. */
|
|
struct bucket *hole = find_empty_bucket(map, neighborhood, MAX_PROBES);
|
|
|
|
/*
|
|
* Keep trying until the empty bucket is in the bucket's neighborhood or we are unable to
|
|
* move it any closer by swapping it with a filled bucket.
|
|
*/
|
|
while (hole != NULL) {
|
|
int distance = hole - neighborhood;
|
|
|
|
if (distance < NEIGHBORHOOD) {
|
|
/*
|
|
* We've found or relocated an empty bucket close enough to the initial
|
|
* hash bucket to be referenced by its hop vector.
|
|
*/
|
|
return hole;
|
|
}
|
|
|
|
/*
|
|
* The nearest empty bucket isn't within the neighborhood that must contain the new
|
|
* entry, so try to swap it with bucket that is closer.
|
|
*/
|
|
hole = move_empty_bucket(map, hole);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* vdo_int_map_put() - Try to associate a value with an integer.
|
|
* @map: The int_map to attempt to modify.
|
|
* @key: The key with which to associate the new value.
|
|
* @new_value: The value to be associated with the key.
|
|
* @update: Whether to overwrite an existing value.
|
|
* @old_value_ptr: A pointer in which to store either the old value (if the key was already mapped)
|
|
* or NULL if the map did not contain the key; NULL may be provided if the caller
|
|
* does not need to know the old value
|
|
*
|
|
* Try to associate a value (a pointer) with an integer in an int_map. If the map already contains
|
|
* a mapping for the provided key, the old value is only replaced with the specified value if
|
|
* update is true. In either case the old value is returned. If the map does not already contain a
|
|
* value for the specified key, the new value is added regardless of the value of update.
|
|
*
|
|
* Return: VDO_SUCCESS or an error code.
|
|
*/
|
|
int vdo_int_map_put(struct int_map *map, u64 key, void *new_value, bool update,
|
|
void **old_value_ptr)
|
|
{
|
|
struct bucket *neighborhood, *bucket;
|
|
|
|
if (unlikely(new_value == NULL))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Select the bucket at the start of the neighborhood that must contain any entry for the
|
|
* provided key.
|
|
*/
|
|
neighborhood = select_bucket(map, key);
|
|
|
|
/*
|
|
* Check whether the neighborhood already contains an entry for the key, in which case we
|
|
* optionally update it, returning the old value.
|
|
*/
|
|
if (update_mapping(map, neighborhood, key, new_value, update, old_value_ptr))
|
|
return VDO_SUCCESS;
|
|
|
|
/*
|
|
* Find an empty bucket in the desired neighborhood for the new entry or re-arrange entries
|
|
* in the map so there is such a bucket. This operation will usually succeed; the loop body
|
|
* will only be executed on the rare occasions that we have to resize the map.
|
|
*/
|
|
while ((bucket = find_or_make_vacancy(map, neighborhood)) == NULL) {
|
|
int result;
|
|
|
|
/*
|
|
* There is no empty bucket in which to put the new entry in the current map, so
|
|
* we're forced to allocate a new bucket array with a larger capacity, re-hash all
|
|
* the entries into those buckets, and try again (a very expensive operation for
|
|
* large maps).
|
|
*/
|
|
result = resize_buckets(map);
|
|
if (result != VDO_SUCCESS)
|
|
return result;
|
|
|
|
/*
|
|
* Resizing the map invalidates all pointers to buckets, so recalculate the
|
|
* neighborhood pointer.
|
|
*/
|
|
neighborhood = select_bucket(map, key);
|
|
}
|
|
|
|
/* Put the new entry in the empty bucket, adding it to the neighborhood. */
|
|
bucket->key = key;
|
|
bucket->value = new_value;
|
|
insert_in_hop_list(neighborhood, bucket);
|
|
map->size += 1;
|
|
|
|
/* There was no existing entry, so there was no old value to be returned. */
|
|
if (old_value_ptr != NULL)
|
|
*old_value_ptr = NULL;
|
|
return VDO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* vdo_int_map_remove() - Remove the mapping for a given key from the int_map.
|
|
* @map: The int_map from which to remove the mapping.
|
|
* @key: The key whose mapping is to be removed.
|
|
*
|
|
* Return: the value that was associated with the key, or NULL if it was not mapped.
|
|
*/
|
|
void *vdo_int_map_remove(struct int_map *map, u64 key)
|
|
{
|
|
void *value;
|
|
|
|
/* Select the bucket to search and search it for an existing entry. */
|
|
struct bucket *bucket = select_bucket(map, key);
|
|
struct bucket *previous;
|
|
struct bucket *victim = search_hop_list(map, bucket, key, &previous);
|
|
|
|
if (victim == NULL) {
|
|
/* There is no matching entry to remove. */
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* We found an entry to remove. Save the mapped value to return later and empty the bucket.
|
|
*/
|
|
map->size -= 1;
|
|
value = victim->value;
|
|
victim->value = NULL;
|
|
victim->key = 0;
|
|
|
|
/* The victim bucket is now empty, but it still needs to be spliced out of the hop list. */
|
|
if (previous == NULL) {
|
|
/* The victim is the head of the list, so swing first_hop. */
|
|
bucket->first_hop = victim->next_hop;
|
|
} else {
|
|
previous->next_hop = victim->next_hop;
|
|
}
|
|
|
|
victim->next_hop = NULL_HOP_OFFSET;
|
|
return value;
|
|
}
|