]> git.proxmox.com Git - ceph.git/blob - ceph/src/spdk/ocf/src/promotion/nhit/nhit_hash.c
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / spdk / ocf / src / promotion / nhit / nhit_hash.c
1 /*
2 * Copyright(c) 2019 Intel Corporation
3 * SPDX-License-Identifier: BSD-3-Clause-Clear
4 */
5
6 #include "../../ocf_priv.h"
7
8 #include "nhit_hash.h"
9
10 /* Implementation of hashmap-ish structure for tracking core lines in nhit
11 * promotion policy. It consists of two arrays:
12 * - hash_map - indexed by hash formed from core id and core lba pairs,
13 * contains pointers (indices) to the ring buffer. Each index in this array
14 * has its own rwsem.
15 * - ring_buffer - contains per-coreline metadata and collision info for
16 * open addressing. If we run out of space in this array, we just loop around
17 * and insert elements from the beggining. So lifetime of a core line varies
18 * depending on insertion and removal rate.
19 *
20 * and rb_pointer which is index to ring_buffer element that is going to be used
21 * for next insertion.
22 *
23 * Operations:
24 * - query(core_id, core_lba):
25 * Check if core line is present in structure, bump up counter and
26 * return its value.
27 *
28 * - insertion(core_id, core_lba):
29 * Insert new core line into structure
30 * 1. get new slot from ring buffer
31 * a. check if current slot under rb_pointer is valid
32 * and if not - exit
33 * b. set current slot as invalid and increment rb_pointer
34 * 2. lock hash bucket for new item and for ring buffer slot
35 * (if non-empty) in ascending bucket id order (to avoid deadlock)
36 * 3. insert new data, add to collision
37 * 4. unlock both hash buckets
38 * 5. commit rb_slot (mark it as valid)
39 *
40 * Insertion explained visually:
41 *
42 * Suppose that we want to add a new core line with hash value H which already has
43 * some colliding core lines
44 *
45 * hash(core_id, core_lba)
46 * +
47 * |
48 * v
49 * +--+--+--+--+--+--+--+--++-+--+
50 * | | |I | | | | | |H | | hash_map
51 * +--+--++-+--+--+--+--+--++-+--+
52 * __| rb_pointer | _______
53 * | + | | |
54 * v v v | v
55 * +--++-+--+---+-+--+--+--++-+--+--++-+--++-+
56 * | | | | |X | | | | | | | | | | ring_buffer
57 * +--++-+--+---+-+--+--+--++-+--+--++-+--+--+
58 * | ^ | ^
59 * |________| |________|
60 *
61 * We will attempt to insert new element at rb_pointer. Since rb_pointer is
62 * pointing to occupied rb slot we need to write-lock hash bucket I associated
63 * with this slot and remove it from collision list. We've gained an empty slot
64 * and we use slot X for new hash H entry.
65 *
66 * +--+--+--+--+--+--+--+--+--+--+
67 * | | |I | | | | | |H | | hash_map
68 * +--+--++-+--+--+--+--+--++-+--+
69 * __| rb_pointer | _______
70 * | + | | |
71 * v v v | v
72 * +--++-+--+-----++-+--+--++-+--+--++-+--++-+
73 * | | | | |X | | | | | | | | | | ring_buffer
74 * +--+--+--+---+-+--+--+--++-+--+--++-+--++-+
75 * ^ | ^ |
76 * | |________| |
77 * |__________________________|
78 *
79 * Valid field in nhit_list_elem is guarded by rb_pointer_lock to make sure we
80 * won't try to use the same slot in two threads. That would be possible if in
81 * time between removal from collision and insertion into the new one the
82 * rb_pointer would go around the whole structure (likeliness depends on size of
83 * ring_buffer).
84 */
85
86 #define HASH_PRIME 4099
87
88 struct nhit_list_elem {
89 /* Fields are ordered for memory efficiency, not for looks. */
90 uint64_t core_lba;
91 env_atomic counter;
92 ocf_cache_line_t coll_prev;
93 ocf_cache_line_t coll_next;
94 ocf_core_id_t core_id;
95 bool valid;
96 };
97
98 struct nhit_hash {
99 ocf_cache_line_t hash_entries;
100 uint64_t rb_entries;
101
102 ocf_cache_line_t *hash_map;
103 env_rwsem *hash_locks;
104
105 struct nhit_list_elem *ring_buffer;
106 uint64_t rb_pointer;
107 env_spinlock rb_pointer_lock;
108 };
109
110 static uint64_t calculate_hash_buckets(uint64_t hash_size)
111 {
112 return OCF_DIV_ROUND_UP(hash_size / 4, HASH_PRIME) * HASH_PRIME - 1;
113 }
114
115 uint64_t nhit_hash_sizeof(uint64_t hash_size)
116 {
117 uint64_t size = 0;
118 uint64_t n_buckets = calculate_hash_buckets(hash_size);
119
120 size += sizeof(struct nhit_hash);
121
122 size += n_buckets * sizeof(ocf_cache_line_t);
123 size += n_buckets * sizeof(env_rwsem);
124
125 size += hash_size * sizeof(struct nhit_list_elem);
126
127 return size;
128 }
129
130 ocf_error_t nhit_hash_init(uint64_t hash_size, nhit_hash_t *ctx)
131 {
132 int result = 0;
133 struct nhit_hash *new_ctx;
134 uint32_t i;
135 int64_t i_locks;
136
137 new_ctx = env_vzalloc(sizeof(*new_ctx));
138 if (!new_ctx) {
139 result = -OCF_ERR_NO_MEM;
140 goto exit;
141 }
142
143 new_ctx->rb_entries = hash_size;
144 new_ctx->hash_entries = calculate_hash_buckets(hash_size);
145
146 new_ctx->hash_map = env_vzalloc(
147 new_ctx->hash_entries * sizeof(*new_ctx->hash_map));
148 if (!new_ctx->hash_map) {
149 result = -OCF_ERR_NO_MEM;
150 goto dealloc_ctx;
151 }
152 for (i = 0; i < new_ctx->hash_entries; i++)
153 new_ctx->hash_map[i] = new_ctx->rb_entries;
154
155 new_ctx->hash_locks = env_vzalloc(
156 new_ctx->hash_entries * sizeof(*new_ctx->hash_locks));
157 if (!new_ctx->hash_locks) {
158 result = -OCF_ERR_NO_MEM;
159 goto dealloc_hash;
160 }
161
162 for (i_locks = 0; i_locks < new_ctx->hash_entries; i_locks++) {
163 if (env_rwsem_init(&new_ctx->hash_locks[i_locks])) {
164 result = -OCF_ERR_UNKNOWN;
165 goto dealloc_locks;
166 }
167 }
168
169 new_ctx->ring_buffer = env_vzalloc(
170 new_ctx->rb_entries * sizeof(*new_ctx->ring_buffer));
171 if (!new_ctx->ring_buffer) {
172 result = -OCF_ERR_NO_MEM;
173 goto dealloc_locks;
174 }
175 for (i = 0; i < new_ctx->rb_entries; i++) {
176 new_ctx->ring_buffer[i].core_id = OCF_CORE_ID_INVALID;
177 new_ctx->ring_buffer[i].valid = true;
178 env_atomic_set(&new_ctx->ring_buffer[i].counter, 0);
179 }
180
181 result = env_spinlock_init(&new_ctx->rb_pointer_lock);
182 if (result)
183 goto dealloc_locks;
184
185 new_ctx->rb_pointer = 0;
186
187 *ctx = new_ctx;
188 return 0;
189
190 dealloc_locks:
191 while (i_locks--)
192 ENV_BUG_ON(env_rwsem_destroy(&new_ctx->hash_locks[i_locks]));
193 env_vfree(new_ctx->hash_locks);
194 dealloc_hash:
195 env_vfree(new_ctx->hash_map);
196 dealloc_ctx:
197 env_vfree(new_ctx);
198 exit:
199 return result;
200 }
201
202 void nhit_hash_deinit(nhit_hash_t ctx)
203 {
204 ocf_cache_line_t i;
205
206 env_spinlock_destroy(&ctx->rb_pointer_lock);
207 for (i = 0; i < ctx->hash_entries; i++)
208 ENV_BUG_ON(env_rwsem_destroy(&ctx->hash_locks[i]));
209
210 env_vfree(ctx->ring_buffer);
211 env_vfree(ctx->hash_locks);
212 env_vfree(ctx->hash_map);
213 env_vfree(ctx);
214 }
215
216 static ocf_cache_line_t hash_function(ocf_core_id_t core_id, uint64_t core_lba,
217 uint64_t limit)
218 {
219 if (core_id == OCF_CORE_ID_INVALID)
220 return limit;
221
222 return (ocf_cache_line_t) ((core_lba * HASH_PRIME + core_id) % limit);
223 }
224
225 static ocf_cache_line_t core_line_lookup(nhit_hash_t ctx,
226 ocf_core_id_t core_id, uint64_t core_lba)
227 {
228 ocf_cache_line_t hash = hash_function(core_id, core_lba,
229 ctx->hash_entries);
230 ocf_cache_line_t needle = ctx->rb_entries;
231 ocf_cache_line_t cur;
232
233 for (cur = ctx->hash_map[hash]; cur != ctx->rb_entries;
234 cur = ctx->ring_buffer[cur].coll_next) {
235 struct nhit_list_elem *cur_elem = &ctx->ring_buffer[cur];
236
237 if (cur_elem->core_lba == core_lba &&
238 cur_elem->core_id == core_id) {
239 needle = cur;
240 break;
241 }
242 }
243
244 return needle;
245 }
246
247 static inline bool get_rb_slot(nhit_hash_t ctx, uint64_t *slot)
248 {
249 bool result = true;
250
251 OCF_CHECK_NULL(slot);
252
253 env_spinlock_lock(&ctx->rb_pointer_lock);
254
255 *slot = ctx->rb_pointer;
256 result = ctx->ring_buffer[*slot].valid;
257
258 ctx->ring_buffer[*slot].valid = false;
259
260 ctx->rb_pointer = (*slot + 1) % ctx->rb_entries;
261
262 env_spinlock_unlock(&ctx->rb_pointer_lock);
263
264 return result;
265 }
266
267 static inline void commit_rb_slot(nhit_hash_t ctx, uint64_t slot)
268 {
269 env_spinlock_lock(&ctx->rb_pointer_lock);
270
271 ctx->ring_buffer[slot].valid = true;
272
273 env_spinlock_unlock(&ctx->rb_pointer_lock);
274 }
275
276 static void collision_remove(nhit_hash_t ctx, uint64_t slot_id)
277 {
278 struct nhit_list_elem *slot = &ctx->ring_buffer[slot_id];
279 ocf_cache_line_t hash = hash_function(slot->core_id, slot->core_lba,
280 ctx->hash_entries);
281
282 if (slot->core_id == OCF_CORE_ID_INVALID)
283 return;
284
285 slot->core_id = OCF_CORE_ID_INVALID;
286
287 if (slot->coll_prev != ctx->rb_entries)
288 ctx->ring_buffer[slot->coll_prev].coll_next = slot->coll_next;
289
290 if (slot->coll_next != ctx->rb_entries)
291 ctx->ring_buffer[slot->coll_next].coll_prev = slot->coll_prev;
292
293 if (ctx->hash_map[hash] == slot_id)
294 ctx->hash_map[hash] = slot->coll_next;
295 }
296
297 static void collision_insert_new(nhit_hash_t ctx,
298 uint64_t slot_id, ocf_core_id_t core_id,
299 uint64_t core_lba)
300 {
301 ocf_cache_line_t hash = hash_function(core_id, core_lba,
302 ctx->hash_entries);
303 struct nhit_list_elem *slot = &ctx->ring_buffer[slot_id];
304
305 slot->core_id = core_id;
306 slot->core_lba = core_lba;
307 slot->coll_next = ctx->hash_map[hash];
308 slot->coll_prev = ctx->rb_entries;
309 env_atomic_set(&slot->counter, 1);
310
311 if (ctx->hash_map[hash] != ctx->rb_entries)
312 ctx->ring_buffer[ctx->hash_map[hash]].coll_prev = slot_id;
313
314 ctx->hash_map[hash] = slot_id;
315 }
316
317 static inline void write_lock_hashes(nhit_hash_t ctx, ocf_core_id_t core_id1,
318 uint64_t core_lba1, ocf_core_id_t core_id2, uint64_t core_lba2)
319 {
320 ocf_cache_line_t hash1 = hash_function(core_id1, core_lba1,
321 ctx->hash_entries);
322 ocf_cache_line_t hash2 = hash_function(core_id2, core_lba2,
323 ctx->hash_entries);
324 ocf_cache_line_t lock_order[2] = {
325 OCF_MIN(hash1, hash2),
326 OCF_MAX(hash1, hash2)};
327
328 if (lock_order[0] != ctx->hash_entries)
329 env_rwsem_down_write(&ctx->hash_locks[lock_order[0]]);
330
331 if ((lock_order[1] != ctx->hash_entries) && (lock_order[0] != lock_order[1]))
332 env_rwsem_down_write(&ctx->hash_locks[lock_order[1]]);
333 }
334
335 static inline void write_unlock_hashes(nhit_hash_t ctx, ocf_core_id_t core_id1,
336 uint64_t core_lba1, ocf_core_id_t core_id2, uint64_t core_lba2)
337 {
338 ocf_cache_line_t hash1 = hash_function(core_id1, core_lba1,
339 ctx->hash_entries);
340 ocf_cache_line_t hash2 = hash_function(core_id2, core_lba2,
341 ctx->hash_entries);
342
343 if (hash1 != ctx->hash_entries)
344 env_rwsem_up_write(&ctx->hash_locks[hash1]);
345
346 if ((hash2 != ctx->hash_entries) && (hash1 != hash2))
347 env_rwsem_up_write(&ctx->hash_locks[hash2]);
348 }
349
350 void nhit_hash_insert(nhit_hash_t ctx, ocf_core_id_t core_id, uint64_t core_lba)
351 {
352 uint64_t slot_id;
353 struct nhit_list_elem *slot;
354 ocf_core_id_t slot_core_id;
355 uint64_t slot_core_lba;
356
357 if (!get_rb_slot(ctx, &slot_id))
358 return;
359
360 slot = &ctx->ring_buffer[slot_id];
361 slot_core_id = slot->core_id;
362 slot_core_lba = slot->core_lba;
363
364 write_lock_hashes(ctx, core_id, core_lba, slot_core_id, slot_core_lba);
365
366 collision_remove(ctx, slot_id);
367 collision_insert_new(ctx, slot_id, core_id, core_lba);
368
369 write_unlock_hashes(ctx, core_id, core_lba, slot_core_id, slot_core_lba);
370
371 commit_rb_slot(ctx, slot_id);
372 }
373
374 bool nhit_hash_query(nhit_hash_t ctx, ocf_core_id_t core_id, uint64_t core_lba,
375 int32_t *counter)
376 {
377 ocf_cache_line_t hash = hash_function(core_id, core_lba,
378 ctx->hash_entries);
379 uint64_t rb_idx;
380
381 OCF_CHECK_NULL(counter);
382
383 env_rwsem_down_read(&ctx->hash_locks[hash]);
384 rb_idx = core_line_lookup(ctx, core_id, core_lba);
385
386 if (rb_idx == ctx->rb_entries) {
387 env_rwsem_up_read(&ctx->hash_locks[hash]);
388 return false;
389 }
390
391 *counter = env_atomic_inc_return(&ctx->ring_buffer[rb_idx].counter);
392
393 env_rwsem_up_read(&ctx->hash_locks[hash]);
394
395 return true;
396 }
397
398 void nhit_hash_set_occurences(nhit_hash_t ctx, ocf_core_id_t core_id,
399 uint64_t core_lba, int32_t occurences)
400 {
401 ocf_cache_line_t hash = hash_function(core_id, core_lba,
402 ctx->hash_entries);
403 uint64_t rb_idx;
404
405 env_rwsem_down_read(&ctx->hash_locks[hash]);
406 rb_idx = core_line_lookup(ctx, core_id, core_lba);
407
408 if (rb_idx == ctx->rb_entries) {
409 env_rwsem_up_read(&ctx->hash_locks[hash]);
410 return;
411 }
412
413 env_atomic_set(&ctx->ring_buffer[rb_idx].counter, occurences);
414
415 env_rwsem_up_read(&ctx->hash_locks[hash]);
416 }
417