400 lines
11 KiB
C
400 lines
11 KiB
C
/*
|
|
* Copyright(c) 2019 Intel Corporation
|
|
* SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
*/
|
|
|
|
#include "../../ocf_priv.h"
|
|
|
|
#include "nhit_hash.h"
|
|
|
|
/* Implementation of hashmap-ish structure for tracking core lines in nhit
|
|
* promotion policy. It consists of two arrays:
|
|
* - hash_map - indexed by hash formed from core id and core lba pairs,
|
|
* contains pointers (indices) to the ring buffer. Each index in this array
|
|
* has its own rwsem.
|
|
* - ring_buffer - contains per-coreline metadata and collision info for
|
|
* open addressing. If we run out of space in this array, we just loop around
|
|
* and insert elements from the beggining. So lifetime of a core line varies
|
|
* depending on insertion and removal rate.
|
|
*
|
|
* and rb_pointer which is index to ring_buffer element that is going to be used
|
|
* for next insertion.
|
|
*
|
|
* Operations:
|
|
* - query(core_id, core_lba):
|
|
* Check if core line is present in structure, bump up counter and
|
|
* return its value.
|
|
*
|
|
* - insertion(core_id, core_lba):
|
|
* Insert new core line into structure
|
|
* 1. get new slot from ring buffer
|
|
* a. check if current slot under rb_pointer is valid
|
|
* and if not - exit
|
|
* b. set current slot as invalid and increment rb_pointer
|
|
* 2. lock hash bucket for new item and for ring buffer slot
|
|
* (if non-empty) in ascending bucket id order (to avoid deadlock)
|
|
* 3. insert new data, add to collision
|
|
* 4. unlock both hash buckets
|
|
* 5. commit rb_slot (mark it as valid)
|
|
*
|
|
* Insertion explained visually:
|
|
*
|
|
* Suppose that we want to add a new core line with hash value H which already has
|
|
* some colliding core lines
|
|
*
|
|
* hash(core_id, core_lba)
|
|
* +
|
|
* |
|
|
* v
|
|
* +--+--+--+--+--+--+--+--++-+--+
|
|
* | | |I | | | | | |H | | hash_map
|
|
* +--+--++-+--+--+--+--+--++-+--+
|
|
* __| rb_pointer | _______
|
|
* | + | | |
|
|
* v v v | v
|
|
* +--++-+--+---+-+--+--+--++-+--+--++-+--++-+
|
|
* | | | | |X | | | | | | | | | | ring_buffer
|
|
* +--++-+--+---+-+--+--+--++-+--+--++-+--+--+
|
|
* | ^ | ^
|
|
* |________| |________|
|
|
*
|
|
* We will attempt to insert new element at rb_pointer. Since rb_pointer is
|
|
* pointing to occupied rb slot we need to write-lock hash bucket I associated
|
|
* with this slot and remove it from collision list. We've gained an empty slot
|
|
* and we use slot X for new hash H entry.
|
|
*
|
|
* +--+--+--+--+--+--+--+--+--+--+
|
|
* | | |I | | | | | |H | | hash_map
|
|
* +--+--++-+--+--+--+--+--++-+--+
|
|
* __| rb_pointer | _______
|
|
* | + | | |
|
|
* v v v | v
|
|
* +--++-+--+-----++-+--+--++-+--+--++-+--++-+
|
|
* | | | | |X | | | | | | | | | | ring_buffer
|
|
* +--+--+--+---+-+--+--+--++-+--+--++-+--++-+
|
|
* ^ | ^ |
|
|
* | |________| |
|
|
* |__________________________|
|
|
*
|
|
* Valid field in nhit_list_elem is guarded by rb_pointer_lock to make sure we
|
|
* won't try to use the same slot in two threads. That would be possible if in
|
|
* time between removal from collision and insertion into the new one the
|
|
* rb_pointer would go around the whole structure (likeliness depends on size of
|
|
* ring_buffer).
|
|
*/
|
|
|
|
#define HASH_PRIME 4099
|
|
|
|
struct nhit_list_elem {
|
|
ocf_core_id_t core_id;
|
|
uint64_t core_lba;
|
|
|
|
ocf_cache_line_t coll_prev;
|
|
ocf_cache_line_t coll_next;
|
|
|
|
bool valid;
|
|
|
|
env_atomic counter;
|
|
};
|
|
|
|
struct nhit_hash {
|
|
ocf_cache_line_t hash_entries;
|
|
uint64_t rb_entries;
|
|
|
|
ocf_cache_line_t *hash_map;
|
|
env_rwsem *hash_locks;
|
|
|
|
struct nhit_list_elem *ring_buffer;
|
|
uint64_t rb_pointer;
|
|
env_spinlock rb_pointer_lock;
|
|
};
|
|
|
|
ocf_error_t nhit_hash_init(uint64_t hash_size, nhit_hash_t *ctx)
|
|
{
|
|
int result = 0;
|
|
struct nhit_hash *new_ctx;
|
|
uint32_t i;
|
|
int64_t i_locks;
|
|
|
|
new_ctx = env_vzalloc(sizeof(*new_ctx));
|
|
if (!new_ctx) {
|
|
result = -OCF_ERR_NO_MEM;
|
|
goto exit;
|
|
}
|
|
|
|
new_ctx->rb_entries = hash_size;
|
|
new_ctx->hash_entries = OCF_DIV_ROUND_UP(
|
|
new_ctx->rb_entries / 4,
|
|
HASH_PRIME) * HASH_PRIME - 1;
|
|
|
|
new_ctx->hash_map = env_vzalloc(
|
|
new_ctx->hash_entries * sizeof(*new_ctx->hash_map));
|
|
if (!new_ctx->hash_map) {
|
|
result = -OCF_ERR_NO_MEM;
|
|
goto dealloc_ctx;
|
|
}
|
|
for (i = 0; i < new_ctx->hash_entries; i++)
|
|
new_ctx->hash_map[i] = new_ctx->rb_entries;
|
|
|
|
new_ctx->hash_locks = env_vzalloc(
|
|
new_ctx->hash_entries * sizeof(*new_ctx->hash_locks));
|
|
if (!new_ctx->hash_locks) {
|
|
result = -OCF_ERR_NO_MEM;
|
|
goto dealloc_hash;
|
|
}
|
|
|
|
for (i_locks = 0; i_locks < new_ctx->hash_entries; i_locks++) {
|
|
if (env_rwsem_init(&new_ctx->hash_locks[i_locks])) {
|
|
result = -OCF_ERR_UNKNOWN;
|
|
i_locks--;
|
|
goto dealloc_locks;
|
|
}
|
|
}
|
|
|
|
new_ctx->ring_buffer = env_vzalloc(
|
|
new_ctx->rb_entries * sizeof(*new_ctx->ring_buffer));
|
|
if (!new_ctx->ring_buffer) {
|
|
result = -OCF_ERR_NO_MEM;
|
|
goto dealloc_locks;
|
|
}
|
|
for (i = 0; i < new_ctx->rb_entries; i++) {
|
|
new_ctx->ring_buffer[i].core_id = OCF_CORE_ID_INVALID;
|
|
new_ctx->ring_buffer[i].valid = true;
|
|
env_atomic_set(&new_ctx->ring_buffer[i].counter, 0);
|
|
}
|
|
|
|
env_spinlock_init(&new_ctx->rb_pointer_lock);
|
|
new_ctx->rb_pointer = 0;
|
|
|
|
*ctx = new_ctx;
|
|
return 0;
|
|
|
|
dealloc_locks:
|
|
for (; i_locks >= 0; i_locks--)
|
|
ENV_BUG_ON(env_rwsem_destroy(&new_ctx->hash_locks[i_locks]));
|
|
env_vfree(new_ctx->hash_locks);
|
|
dealloc_hash:
|
|
env_vfree(new_ctx->hash_map);
|
|
dealloc_ctx:
|
|
env_vfree(new_ctx);
|
|
exit:
|
|
return result;
|
|
}
|
|
|
|
void nhit_hash_deinit(nhit_hash_t ctx)
|
|
{
|
|
ocf_cache_line_t i;
|
|
|
|
env_spinlock_destroy(&ctx->rb_pointer_lock);
|
|
for (i = 0; i < ctx->hash_entries; i++)
|
|
ENV_BUG_ON(env_rwsem_destroy(&ctx->hash_locks[i]));
|
|
|
|
env_vfree(ctx->ring_buffer);
|
|
env_vfree(ctx->hash_locks);
|
|
env_vfree(ctx->hash_map);
|
|
env_vfree(ctx);
|
|
}
|
|
|
|
static ocf_cache_line_t hash_function(ocf_core_id_t core_id, uint64_t core_lba,
|
|
uint64_t limit)
|
|
{
|
|
if (core_id == OCF_CORE_ID_INVALID)
|
|
return limit;
|
|
|
|
return (ocf_cache_line_t) ((core_lba * HASH_PRIME + core_id) % limit);
|
|
}
|
|
|
|
static ocf_cache_line_t core_line_lookup(nhit_hash_t ctx,
|
|
ocf_core_id_t core_id, uint64_t core_lba)
|
|
{
|
|
ocf_cache_line_t hash = hash_function(core_id, core_lba,
|
|
ctx->hash_entries);
|
|
ocf_cache_line_t needle = ctx->rb_entries;
|
|
ocf_cache_line_t cur;
|
|
|
|
for (cur = ctx->hash_map[hash]; cur != ctx->rb_entries;
|
|
cur = ctx->ring_buffer[cur].coll_next) {
|
|
struct nhit_list_elem *cur_elem = &ctx->ring_buffer[cur];
|
|
|
|
if (cur_elem->core_lba == core_lba &&
|
|
cur_elem->core_id == core_id) {
|
|
needle = cur;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return needle;
|
|
}
|
|
|
|
static inline bool get_rb_slot(nhit_hash_t ctx, uint64_t *slot)
|
|
{
|
|
bool result = true;
|
|
|
|
OCF_CHECK_NULL(slot);
|
|
|
|
env_spinlock_lock(&ctx->rb_pointer_lock);
|
|
|
|
*slot = ctx->rb_pointer;
|
|
result = ctx->ring_buffer[*slot].valid;
|
|
|
|
ctx->ring_buffer[*slot].valid = false;
|
|
|
|
ctx->rb_pointer = (*slot + 1) % ctx->rb_entries;
|
|
|
|
env_spinlock_unlock(&ctx->rb_pointer_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static inline void commit_rb_slot(nhit_hash_t ctx, uint64_t slot)
|
|
{
|
|
env_spinlock_lock(&ctx->rb_pointer_lock);
|
|
|
|
ctx->ring_buffer[slot].valid = true;
|
|
|
|
env_spinlock_unlock(&ctx->rb_pointer_lock);
|
|
}
|
|
|
|
static void collision_remove(nhit_hash_t ctx, uint64_t slot_id)
|
|
{
|
|
struct nhit_list_elem *slot = &ctx->ring_buffer[slot_id];
|
|
ocf_cache_line_t hash = hash_function(slot->core_id, slot->core_lba,
|
|
ctx->hash_entries);
|
|
|
|
if (slot->core_id == OCF_CORE_ID_INVALID)
|
|
return;
|
|
|
|
slot->core_id = OCF_CORE_ID_INVALID;
|
|
|
|
if (slot->coll_prev != ctx->rb_entries)
|
|
ctx->ring_buffer[slot->coll_prev].coll_next = slot->coll_next;
|
|
|
|
if (slot->coll_next != ctx->rb_entries)
|
|
ctx->ring_buffer[slot->coll_next].coll_prev = slot->coll_prev;
|
|
|
|
if (ctx->hash_map[hash] == slot_id)
|
|
ctx->hash_map[hash] = slot->coll_next;
|
|
}
|
|
|
|
static void collision_insert_new(nhit_hash_t ctx,
|
|
uint64_t slot_id, ocf_core_id_t core_id,
|
|
uint64_t core_lba)
|
|
{
|
|
ocf_cache_line_t hash = hash_function(core_id, core_lba,
|
|
ctx->hash_entries);
|
|
struct nhit_list_elem *slot = &ctx->ring_buffer[slot_id];
|
|
|
|
slot->core_id = core_id;
|
|
slot->core_lba = core_lba;
|
|
slot->coll_next = ctx->hash_map[hash];
|
|
slot->coll_prev = ctx->rb_entries;
|
|
env_atomic_set(&slot->counter, 1);
|
|
|
|
if (ctx->hash_map[hash] != ctx->rb_entries)
|
|
ctx->ring_buffer[ctx->hash_map[hash]].coll_prev = slot_id;
|
|
|
|
ctx->hash_map[hash] = slot_id;
|
|
}
|
|
|
|
static inline void write_lock_hashes(nhit_hash_t ctx, ocf_core_id_t core_id1,
|
|
uint64_t core_lba1, ocf_core_id_t core_id2, uint64_t core_lba2)
|
|
{
|
|
ocf_cache_line_t hash1 = hash_function(core_id1, core_lba1,
|
|
ctx->hash_entries);
|
|
ocf_cache_line_t hash2 = hash_function(core_id2, core_lba2,
|
|
ctx->hash_entries);
|
|
ocf_cache_line_t lock_order[2] = {
|
|
OCF_MIN(hash1, hash2),
|
|
OCF_MAX(hash1, hash2)};
|
|
|
|
if (lock_order[0] != ctx->hash_entries)
|
|
env_rwsem_down_write(&ctx->hash_locks[lock_order[0]]);
|
|
|
|
if (lock_order[1] != ctx->hash_entries)
|
|
env_rwsem_down_write(&ctx->hash_locks[lock_order[1]]);
|
|
}
|
|
|
|
static inline void write_unlock_hashes(nhit_hash_t ctx, ocf_core_id_t core_id1,
|
|
uint64_t core_lba1, ocf_core_id_t core_id2, uint64_t core_lba2)
|
|
{
|
|
ocf_cache_line_t hash1 = hash_function(core_id1, core_lba1,
|
|
ctx->hash_entries);
|
|
ocf_cache_line_t hash2 = hash_function(core_id2, core_lba2,
|
|
ctx->hash_entries);
|
|
|
|
if (hash1 != ctx->hash_entries)
|
|
env_rwsem_up_write(&ctx->hash_locks[hash1]);
|
|
|
|
if (hash2 != ctx->hash_entries)
|
|
env_rwsem_up_write(&ctx->hash_locks[hash2]);
|
|
}
|
|
|
|
void nhit_hash_insert(nhit_hash_t ctx, ocf_core_id_t core_id, uint64_t core_lba)
|
|
{
|
|
uint64_t slot_id;
|
|
struct nhit_list_elem *slot;
|
|
ocf_core_id_t slot_core_id;
|
|
uint64_t slot_core_lba;
|
|
|
|
if (!get_rb_slot(ctx, &slot_id))
|
|
return;
|
|
|
|
slot = &ctx->ring_buffer[slot_id];
|
|
slot_core_id = slot->core_id;
|
|
slot_core_lba = slot->core_lba;
|
|
|
|
write_lock_hashes(ctx, core_id, core_lba, slot_core_id, slot_core_lba);
|
|
|
|
collision_remove(ctx, slot_id);
|
|
collision_insert_new(ctx, slot_id, core_id, core_lba);
|
|
|
|
write_unlock_hashes(ctx, core_id, core_lba, slot_core_id, slot_core_lba);
|
|
|
|
commit_rb_slot(ctx, slot_id);
|
|
}
|
|
|
|
bool nhit_hash_query(nhit_hash_t ctx, ocf_core_id_t core_id, uint64_t core_lba,
|
|
int32_t *counter)
|
|
{
|
|
ocf_cache_line_t hash = hash_function(core_id, core_lba,
|
|
ctx->hash_entries);
|
|
uint64_t rb_idx;
|
|
|
|
OCF_CHECK_NULL(counter);
|
|
|
|
env_rwsem_down_read(&ctx->hash_locks[hash]);
|
|
rb_idx = core_line_lookup(ctx, core_id, core_lba);
|
|
|
|
if (rb_idx == ctx->rb_entries) {
|
|
env_rwsem_up_read(&ctx->hash_locks[hash]);
|
|
return false;
|
|
}
|
|
|
|
*counter = env_atomic_inc_return(&ctx->ring_buffer[rb_idx].counter);
|
|
|
|
env_rwsem_up_read(&ctx->hash_locks[hash]);
|
|
|
|
return true;
|
|
}
|
|
|
|
void nhit_hash_set_occurences(nhit_hash_t ctx, ocf_core_id_t core_id,
|
|
uint64_t core_lba, int32_t occurences)
|
|
{
|
|
ocf_cache_line_t hash = hash_function(core_id, core_lba,
|
|
ctx->hash_entries);
|
|
uint64_t rb_idx;
|
|
|
|
env_rwsem_down_read(&ctx->hash_locks[hash]);
|
|
rb_idx = core_line_lookup(ctx, core_id, core_lba);
|
|
|
|
if (rb_idx == ctx->rb_entries) {
|
|
env_rwsem_up_read(&ctx->hash_locks[hash]);
|
|
return;
|
|
}
|
|
|
|
env_atomic_set(&ctx->ring_buffer[rb_idx].counter, occurences);
|
|
|
|
env_rwsem_up_read(&ctx->hash_locks[hash]);
|
|
}
|
|
|