mirror of
https://gitflic.ru/project/erthink/libmdbx.git
synced 2025-02-13 02:35:51 +00:00
mdbx: добавление rkl с итераторами.
RKL — сортированный набор txnid, использующий внутри комбинацию непрерывного интервала и списка. Обеспечивает хранение id записей при переработке, очистку и обновлении GC, включая возврат остатков переработанных страниц. Итератор для RKL — обеспечивает изоляцию внутреннего устройства rkl от остального кода, чем существенно его упрощает. Фактически именно использованием rkl с итераторами ликвидируется "ребус" исторически образовавшийся в gc-update. -- При переработке GC записи преимущественно выбираются последовательно, но это не гарантируется. В LIFO-режиме переработка и добавление записей в rkl происходит преимущественно в обратном порядке, но из-за завершения читающих транзакций могут быть «скачки» в прямом направлении. В FIFO-режиме записи GC перерабатываются в прямом порядке и при этом линейно, но не обязательно строго последовательно, при этом гарантируется что между добавляемыми в rkl идентификаторами в GC нет записей, т.е. между первой (минимальный id) и последней (максимальный id) в GC нет записей и весь интервал может быть использован для возврата остатков страниц в GC. Таким образом, комбинация линейного интервала и списка (отсортированного в порядке возрастания элементов) является рациональным решением, близким к теоретически оптимальному пределу. Реализация rkl достаточно проста/прозрачная, если не считать неочевидную «магию» обмена непрерывного интервала и образующихся в списке последовательностей. Однако, именно этот автоматически выполняемый без лишних операций обмен оправдывает все накладные расходы.
This commit is contained in:
parent
9568209ee4
commit
de968541cc
@ -132,6 +132,8 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/preface.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/proto.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/refund.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/rkl.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/rkl.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/sort.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.h"
|
||||
@ -832,6 +834,8 @@ else()
|
||||
"${MDBX_SOURCE_DIR}/preface.h"
|
||||
"${MDBX_SOURCE_DIR}/proto.h"
|
||||
"${MDBX_SOURCE_DIR}/refund.c"
|
||||
"${MDBX_SOURCE_DIR}/rkl.c"
|
||||
"${MDBX_SOURCE_DIR}/rkl.h"
|
||||
"${MDBX_SOURCE_DIR}/sort.h"
|
||||
"${MDBX_SOURCE_DIR}/spill.c"
|
||||
"${MDBX_SOURCE_DIR}/spill.h"
|
||||
|
@ -710,6 +710,7 @@ $(DIST_DIR)/@tmp-internals.inc: $(DIST_DIR)/@tmp-essentials.inc src/version.c $(
|
||||
-e '/#include "essentials.h"/d' \
|
||||
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
|
||||
-e '/#include "proto.h"/r src/proto.h' \
|
||||
-e '/#include "rkl.h"/r src/rkl.h' \
|
||||
-e '/#include "txl.h"/r src/txl.h' \
|
||||
-e '/#include "unaligned.h"/r src/unaligned.h' \
|
||||
-e '/#include "cogs.h"/r src/cogs.h' \
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "page-ops.c"
|
||||
#include "pnl.c"
|
||||
#include "refund.c"
|
||||
#include "rkl.c"
|
||||
#include "spill.c"
|
||||
#include "table.c"
|
||||
#include "tls.c"
|
||||
|
@ -46,6 +46,7 @@ typedef struct bind_reader_slot_result {
|
||||
|
||||
#include "atomics-ops.h"
|
||||
#include "proto.h"
|
||||
#include "rkl.h"
|
||||
#include "txl.h"
|
||||
#include "unaligned.h"
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
462
src/rkl.c
Normal file
462
src/rkl.c
Normal file
@ -0,0 +1,462 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2025
|
||||
|
||||
#include "rkl.h"
|
||||
#include "internals.h"
|
||||
|
||||
static inline size_t rkl_size2bytes(const size_t size) {
|
||||
assert(size > 0 && size <= txl_max * 2);
|
||||
size_t bytes = ceil_powerof2(MDBX_ASSUME_MALLOC_OVERHEAD + sizeof(txnid_t) * size, txl_granulate * sizeof(txnid_t)) -
|
||||
MDBX_ASSUME_MALLOC_OVERHEAD;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static inline size_t rkl_bytes2size(const size_t bytes) {
|
||||
size_t size = bytes / sizeof(txnid_t);
|
||||
assert(size > 0 && size <= txl_max * 2);
|
||||
return size;
|
||||
}
|
||||
|
||||
void rkl_init(rkl_t *rkl) {
|
||||
rkl->list_limit = ARRAY_LENGTH(rkl->inplace);
|
||||
rkl->list = rkl->inplace;
|
||||
rkl_clear(rkl);
|
||||
}
|
||||
|
||||
void rkl_clear(rkl_t *rkl) {
|
||||
rkl->solid_begin = UINT64_MAX;
|
||||
rkl->solid_end = 0;
|
||||
rkl->list_length = 0;
|
||||
}
|
||||
|
||||
void rkl_destroy(rkl_t *rkl) {
|
||||
void *ptr = rkl->list;
|
||||
rkl->list = nullptr;
|
||||
if (ptr != rkl->inplace)
|
||||
osal_free(rkl->list);
|
||||
}
|
||||
|
||||
static inline bool solid_empty(const rkl_t *rkl) { return !(rkl->solid_begin < rkl->solid_end); }
|
||||
|
||||
#define RKL_ORDERED(first, last) ((first) < (last))
|
||||
|
||||
SEARCH_IMPL(rkl_bsearch, txnid_t, txnid_t, RKL_ORDERED)
|
||||
|
||||
void rkl_destructive_move(rkl_t *src, rkl_t *dst) {
|
||||
assert(rkl_check(src));
|
||||
dst->solid_begin = src->solid_begin;
|
||||
dst->solid_end = src->solid_end;
|
||||
dst->list_length = src->list_length;
|
||||
if (dst->list != dst->inplace)
|
||||
osal_free(dst->list);
|
||||
if (src->list_length > ARRAY_LENGTH(dst->inplace)) {
|
||||
assert(src->list != src->inplace);
|
||||
dst->list = src->list;
|
||||
dst->list_limit = src->list_limit;
|
||||
} else {
|
||||
dst->list = dst->inplace;
|
||||
dst->list_limit = ARRAY_LENGTH(src->inplace);
|
||||
memcpy(dst->inplace, src->list, sizeof(dst->inplace));
|
||||
if (src->list != src->inplace)
|
||||
osal_free(src->list);
|
||||
}
|
||||
rkl_init(src);
|
||||
}
|
||||
|
||||
static int rkl_resize(rkl_t *rkl, size_t size) {
|
||||
assert(size > rkl->list_length);
|
||||
assert(rkl_check(rkl));
|
||||
STATIC_ASSERT(txl_max < INT_MAX / sizeof(txnid_t));
|
||||
if (unlikely(size > txl_max)) {
|
||||
ERROR("rkl too long (%zu >= %zu)", size, (size_t)txl_max);
|
||||
return MDBX_TXN_FULL;
|
||||
}
|
||||
if (unlikely(size < rkl->list_length)) {
|
||||
ERROR("unable shrink rkl to %zu since length is %u", size, rkl->list_length);
|
||||
return MDBX_TXN_FULL;
|
||||
}
|
||||
if (unlikely(size <= ARRAY_LENGTH(rkl->inplace))) {
|
||||
if (rkl->list != rkl->inplace) {
|
||||
memcpy(rkl->inplace, rkl->list, sizeof(txnid_t) * rkl->list_length);
|
||||
rkl->list_limit = ARRAY_LENGTH(rkl->inplace);
|
||||
osal_free(rkl->list);
|
||||
rkl->list = rkl->inplace;
|
||||
} else {
|
||||
assert(rkl->list_limit == ARRAY_LENGTH(rkl->inplace));
|
||||
}
|
||||
} else if (size != rkl->list_limit) {
|
||||
size_t bytes = rkl_size2bytes(size);
|
||||
void *const ptr = (rkl->list == rkl->inplace) ? osal_malloc(bytes) : osal_realloc(rkl->list, bytes);
|
||||
if (unlikely(!ptr))
|
||||
return MDBX_ENOMEM;
|
||||
#ifdef osal_malloc_usable_size
|
||||
bytes = osal_malloc_usable_size(ptr);
|
||||
#endif /* osal_malloc_usable_size */
|
||||
rkl->list_limit = rkl_bytes2size(bytes);
|
||||
if (rkl->list == rkl->inplace)
|
||||
memcpy(ptr, rkl->inplace, sizeof(rkl->inplace));
|
||||
rkl->list = ptr;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int rkl_copy(const rkl_t *src, rkl_t *dst) {
|
||||
assert(rkl_check(src));
|
||||
rkl_init(dst);
|
||||
if (!rkl_empty(src)) {
|
||||
if (dst->list_limit < src->list_length) {
|
||||
int err = rkl_resize(dst, src->list_limit);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
}
|
||||
memcpy(dst->list, src->list, sizeof(txnid_t) * src->list_length);
|
||||
dst->list_length = src->list_length;
|
||||
dst->solid_begin = src->solid_begin;
|
||||
dst->solid_end = src->solid_end;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
size_t rkl_len(const rkl_t *rkl) { return rkl_empty(rkl) ? 0 : rkl->solid_end - rkl->solid_begin + rkl->list_length; }
|
||||
|
||||
__hot bool rkl_contain(const rkl_t *rkl, txnid_t id) {
|
||||
assert(rkl_check(rkl));
|
||||
if (!rkl_empty(rkl)) {
|
||||
if (id >= rkl->solid_begin && id < rkl->solid_end)
|
||||
return true;
|
||||
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, id);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
assert(it >= rkl->list && it <= end);
|
||||
if (it != rkl->list)
|
||||
assert(RKL_ORDERED(it[-1], id));
|
||||
if (it != end) {
|
||||
assert(!RKL_ORDERED(it[0], id));
|
||||
return *it == id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline txnid_t list_remove_first(rkl_t *rkl) {
|
||||
assert(rkl->list_length > 0);
|
||||
const txnid_t first = rkl->list[0];
|
||||
if (--rkl->list_length) {
|
||||
/* TODO: Можно подумать о том, чтобы для избавления от memove() добавить headroom или вместо длины и
|
||||
* указателя на список использовать три поля: list_begin, list_end и list_buffer. */
|
||||
size_t i = 0;
|
||||
do
|
||||
rkl->list[i] = rkl->list[i + 1];
|
||||
while (++i <= rkl->list_length);
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
static inline txnid_t after_cut(rkl_t *rkl, const txnid_t out) {
|
||||
if (rkl->list_length == 0 && rkl->solid_begin == rkl->solid_end) {
|
||||
rkl->solid_end = 0;
|
||||
rkl->solid_begin = UINT64_MAX;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static int extend_solid(rkl_t *rkl, txnid_t solid_begin, txnid_t solid_end, const txnid_t id) {
|
||||
if (rkl->list_length) {
|
||||
const txnid_t *i = rkl_bsearch(rkl->list, rkl->list_length, id);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
/* если начало или конец списка примыкает к непрерывному интервалу,
|
||||
* то переносим эти элементы из списка в непрерывный интервал */
|
||||
txnid_t *f = (txnid_t *)i;
|
||||
while (f > rkl->list && f[-1] >= solid_begin - 1) {
|
||||
f -= 1;
|
||||
solid_begin -= 1;
|
||||
if (unlikely(*f != solid_begin))
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
txnid_t *t = (txnid_t *)i;
|
||||
while (t < end && *t <= solid_end) {
|
||||
if (unlikely(*t != solid_end))
|
||||
return MDBX_RESULT_TRUE;
|
||||
solid_end += 1;
|
||||
t += 1;
|
||||
}
|
||||
if (f < t) {
|
||||
rkl->list_length -= t - f;
|
||||
while (t < end)
|
||||
*f++ = *t++;
|
||||
}
|
||||
}
|
||||
|
||||
rkl->solid_begin = solid_begin;
|
||||
rkl->solid_end = solid_end;
|
||||
assert(rkl_check(rkl));
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int rkl_push(rkl_t *rkl, const txnid_t id, const bool known_continuous) {
|
||||
assert(id >= MIN_TXNID && id < INVALID_TXNID);
|
||||
assert(rkl_check(rkl));
|
||||
|
||||
if (rkl->solid_begin >= rkl->solid_end) {
|
||||
/* непрерывный интервал пуст */
|
||||
return extend_solid(rkl, id, id + 1, id);
|
||||
} else if (id < rkl->solid_begin) {
|
||||
if (known_continuous || id + 1 == rkl->solid_begin)
|
||||
/* id примыкает к solid_begin */
|
||||
return extend_solid(rkl, id, rkl->solid_end, id);
|
||||
} else if (id >= rkl->solid_end) {
|
||||
if (known_continuous || id == rkl->solid_end)
|
||||
/* id примыкает к solid_end */
|
||||
return extend_solid(rkl, rkl->solid_begin, id + 1, id);
|
||||
} else {
|
||||
/* id входит в интервал между solid_begin и solid_end, т.е. подан дубликат */
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
if (rkl->list_length == 1 && rkl->solid_end == rkl->solid_begin + 1 &&
|
||||
(rkl->list[0] == id + 1 || rkl->list[0] == id - 1)) {
|
||||
/* В списке один элемент и добавляемый id примыкает к нему, при этом в непрерывном интервале тоже один элемент.
|
||||
* Лучше поменять элементы списка и непрерывного интервала. */
|
||||
const txnid_t couple = (rkl->list[0] == id - 1) ? id - 1 : id;
|
||||
rkl->list[0] = rkl->solid_begin;
|
||||
rkl->solid_begin = couple;
|
||||
rkl->solid_end = couple + 2;
|
||||
assert(rkl_check(rkl));
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
if (unlikely(rkl->list_length == rkl->list_limit)) {
|
||||
/* удваиваем размер буфера если закончилось место */
|
||||
size_t x2 = (rkl->list_limit + 1) << 1;
|
||||
x2 = (x2 > 62) ? x2 : 62;
|
||||
x2 = (x2 < txl_max) ? x2 : txl_max;
|
||||
x2 = (x2 > rkl->list_length) ? x2 : rkl->list_length + 42;
|
||||
int err = rkl_resize(rkl, x2);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
assert(rkl->list_limit > rkl->list_length);
|
||||
}
|
||||
|
||||
size_t i = rkl->list_length;
|
||||
/* ищем место для вставки двигаясь от конца к началу списка, сразу переставляя/раздвигая элементы */
|
||||
while (i > 0) {
|
||||
if (RKL_ORDERED(id, rkl->list[i - 1])) {
|
||||
rkl->list[i] = rkl->list[i - 1];
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
if (unlikely(id == rkl->list[i - 1])) {
|
||||
while (++i < rkl->list_length)
|
||||
rkl->list[i - 1] = rkl->list[i];
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
rkl->list[i] = id;
|
||||
rkl->list_length++;
|
||||
assert(rkl_check(rkl));
|
||||
|
||||
/* После добавления id в списке могла образоваться длинная последовательность,
|
||||
* которую (возможно) стоит обменять с непрерывным интервалом. */
|
||||
if (rkl->list_length > (MDBX_DEBUG ? 2 : 16) &&
|
||||
((i > 0 && rkl->list[i - 1] == id - 1) || (i + 1 < rkl->list_length && rkl->list[i + 1] == id + 1))) {
|
||||
txnid_t new_solid_begin = id;
|
||||
size_t from = i;
|
||||
while (from > 0 && rkl->list[from - 1] == new_solid_begin - 1) {
|
||||
from -= 1;
|
||||
new_solid_begin -= 1;
|
||||
}
|
||||
txnid_t new_solid_end = id + 1;
|
||||
size_t to = i + 1;
|
||||
while (to < rkl->list_length && rkl->list[to] == new_solid_end) {
|
||||
to += 1;
|
||||
new_solid_end += 1;
|
||||
}
|
||||
|
||||
const size_t new_solid_len = to - from;
|
||||
if (new_solid_len > 3) {
|
||||
const size_t old_solid_len = rkl->solid_end - rkl->solid_begin;
|
||||
if (new_solid_len > old_solid_len) {
|
||||
/* Новая непрерывная последовательность длиннее текущей.
|
||||
* Считаем обмен выгодным, если он дешевле пути развития событий с добавлением следующего элемента в список. */
|
||||
const size_t old_solid_pos = rkl_bsearch(rkl->list, rkl->list_length, rkl->solid_begin) - rkl->list;
|
||||
const size_t swap_cost =
|
||||
/* количество элементов списка после изымаемой из списка последовательности,
|
||||
* которые нужно переместить */
|
||||
rkl->list_length - to +
|
||||
/* количество элементов списка после позиции добавляемой в список последовательности,
|
||||
* которые нужно переместить */
|
||||
((from > old_solid_pos) ? from - old_solid_pos : 0)
|
||||
/* количество элементов списка добавляемой последовательности, которые нужно добавить */
|
||||
+ old_solid_len;
|
||||
/* количество элементов списка, которые нужно переместить для вставки еще-одного/следующего элемента */
|
||||
const size_t new_insert_cost = rkl->list_length - i;
|
||||
if (unlikely(swap_cost < new_insert_cost) || MDBX_DEBUG) {
|
||||
/* Изымаемая последовательность длиннее добавляемой, поэтому:
|
||||
* - список станет короче;
|
||||
* - перемещать хвост нужно всегда к началу;
|
||||
* - если начальные элементы потребуется раздвигать,
|
||||
* то места хватит и остающиеся элементы в конце не будут перезаписаны. */
|
||||
size_t moved = 0;
|
||||
if (from > old_solid_pos) {
|
||||
/* добавляемая последовательность ближе к началу, нужно раздвинуть элементы в голове для вставки. */
|
||||
moved = from - old_solid_pos;
|
||||
do {
|
||||
from -= 1;
|
||||
rkl->list[from + old_solid_len] = rkl->list[from];
|
||||
} while (from > old_solid_pos);
|
||||
} else if (from + new_solid_len < old_solid_pos) {
|
||||
/* добавляемая последовательность дальше от начала,
|
||||
* перемещаем часть элементов из хвоста после изымаемой последовательности */
|
||||
do
|
||||
rkl->list[from++] = rkl->list[to++];
|
||||
while (from < old_solid_pos - new_solid_len);
|
||||
}
|
||||
|
||||
/* вставляем последовательноть */
|
||||
i = 0;
|
||||
do
|
||||
rkl->list[from++] = rkl->solid_begin + i++;
|
||||
while (i != old_solid_len);
|
||||
|
||||
/* сдвигаем оставшийся хвост */
|
||||
while (to < rkl->list_length)
|
||||
rkl->list[moved + from++] = rkl->list[to++];
|
||||
|
||||
rkl->list_length = rkl->list_length - new_solid_len + old_solid_len;
|
||||
rkl->solid_begin = new_solid_begin;
|
||||
rkl->solid_end = new_solid_end;
|
||||
assert(rkl_check(rkl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
txnid_t rkl_pop(rkl_t *rkl, const bool highest_not_lowest) {
|
||||
assert(!rkl_empty(rkl));
|
||||
assert(rkl_check(rkl));
|
||||
|
||||
if (rkl->list_length) {
|
||||
assert(rkl->solid_begin <= rkl->solid_end);
|
||||
if (highest_not_lowest && (solid_empty(rkl) || rkl->solid_end < rkl->list[rkl->list_length - 1]))
|
||||
return after_cut(rkl, rkl->list[rkl->list_length -= 1]);
|
||||
if (!highest_not_lowest && (solid_empty(rkl) || rkl->solid_begin > rkl->list[0]))
|
||||
return after_cut(rkl, list_remove_first(rkl));
|
||||
}
|
||||
|
||||
if (!solid_empty(rkl))
|
||||
return after_cut(rkl, highest_not_lowest ? --rkl->solid_end : rkl->solid_begin++);
|
||||
|
||||
assert(rkl_empty(rkl));
|
||||
return 0;
|
||||
}
|
||||
|
||||
txnid_t rkl_lowest(const rkl_t *rkl) {
|
||||
if (rkl->list_length)
|
||||
return (solid_empty(rkl) || rkl->list[0] < rkl->solid_begin) ? rkl->list[0] : rkl->solid_begin;
|
||||
|
||||
return !solid_empty(rkl) ? rkl->solid_begin : INVALID_TXNID;
|
||||
}
|
||||
|
||||
txnid_t rkl_highest(const rkl_t *rkl) {
|
||||
if (rkl->list_length)
|
||||
return (solid_empty(rkl) || rkl->list[rkl->list_length - 1] >= rkl->solid_end) ? rkl->list[rkl->list_length - 1]
|
||||
: rkl->solid_end - 1;
|
||||
|
||||
return !solid_empty(rkl) ? rkl->solid_end - 1 : 0;
|
||||
}
|
||||
|
||||
void rkl_iterator_init(rkl_iterator_t *iter, const rkl_t *rkl, const bool reverse) {
|
||||
iter->pos = reverse ? rkl_len(rkl) : 0;
|
||||
iter->solid_offset = 0;
|
||||
if (!solid_empty(rkl) && rkl->list_length && rkl->solid_begin > rkl->list[0] &&
|
||||
rkl->solid_end < rkl->list[rkl->list_length - 1]) {
|
||||
/* непрерывный интервал "плавает" внутри списка, т.е. находится между какими-то соседними значениями */
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, rkl->solid_begin);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
assert(it > rkl->list && it < end && *it > rkl->solid_begin);
|
||||
iter->solid_offset = it - rkl->list;
|
||||
}
|
||||
}
|
||||
|
||||
txnid_t rkl_iterator_turn(rkl_iterator_t *iter, const rkl_t *rkl, const bool reverse) {
|
||||
size_t pos = iter->pos - !!reverse;
|
||||
if (pos >= rkl_len(rkl))
|
||||
return 0;
|
||||
|
||||
iter->pos = pos + !reverse;
|
||||
assert(iter->pos <= rkl_len(rkl));
|
||||
|
||||
const size_t solid_len = rkl->solid_end - rkl->solid_begin;
|
||||
if (rkl->list_length) {
|
||||
if (solid_len) {
|
||||
if (rkl->solid_end < rkl->list[0]) {
|
||||
if (pos < solid_len)
|
||||
return rkl->solid_begin + pos;
|
||||
pos -= solid_len;
|
||||
assert(pos < rkl->list_length);
|
||||
return rkl->list[pos];
|
||||
} else if (rkl->solid_begin > rkl->list[rkl->list_length - 1]) {
|
||||
if (pos < rkl->list_length)
|
||||
return rkl->list[pos];
|
||||
pos -= rkl->list_length;
|
||||
assert(pos < solid_len);
|
||||
return rkl->solid_begin + pos;
|
||||
} else {
|
||||
assert(rkl->solid_begin > rkl->list[0] && rkl->solid_end < rkl->list[rkl->list_length - 1]);
|
||||
assert(iter->solid_offset > 0 && iter->solid_offset < rkl->list_length);
|
||||
/* непрерывный интервал "плавает" внутри списка, т.е. находится между какими-то соседними значениями */
|
||||
if (pos < iter->solid_offset)
|
||||
return rkl->list[pos];
|
||||
else if (pos < iter->solid_offset + solid_len)
|
||||
return rkl->solid_begin + pos - iter->solid_offset;
|
||||
else
|
||||
return rkl->list[pos - solid_len];
|
||||
}
|
||||
}
|
||||
assert(pos < rkl->list_length);
|
||||
return rkl->list[pos];
|
||||
}
|
||||
assert(pos < solid_len);
|
||||
return rkl->solid_begin + pos;
|
||||
}
|
||||
|
||||
size_t rkl_iterator_left(rkl_iterator_t *iter, const rkl_t *rkl, const bool reverse) {
|
||||
assert(iter->pos <= rkl_len(rkl));
|
||||
return reverse ? iter->pos : rkl_len(rkl) - iter->pos;
|
||||
}
|
||||
|
||||
bool rkl_check(const rkl_t *rkl) {
|
||||
if (!rkl)
|
||||
return false;
|
||||
if (rkl->list == rkl->inplace && unlikely(rkl->list_limit != ARRAY_LENGTH(rkl->inplace)))
|
||||
return false;
|
||||
if (unlikely(rkl->list_limit < ARRAY_LENGTH(rkl->inplace)))
|
||||
return false;
|
||||
|
||||
if (rkl_empty(rkl))
|
||||
return rkl->list_length == 0 && solid_empty(rkl);
|
||||
|
||||
if (rkl->list_length) {
|
||||
for (size_t i = 1; i < rkl->list_length; ++i)
|
||||
if (unlikely(!RKL_ORDERED(rkl->list[i - 1], rkl->list[i])))
|
||||
return false;
|
||||
if (!solid_empty(rkl) && rkl->solid_begin - 1 <= rkl->list[rkl->list_length - 1] &&
|
||||
rkl->solid_end >= rkl->list[0]) {
|
||||
/* непрерывный интервал "плавает" внутри списка, т.е. находится между какими-то соседними значениями */
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, rkl->solid_begin);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
if (it < rkl->list || it > end)
|
||||
return false;
|
||||
if (it > rkl->list && it[-1] >= rkl->solid_begin)
|
||||
return false;
|
||||
if (it < end && it[0] <= rkl->solid_end)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
64
src/rkl.h
Normal file
64
src/rkl.h
Normal file
@ -0,0 +1,64 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
/* Сортированный набор txnid, использующий внутри комбинацию непрерывного интервала и списка.
|
||||
* Обеспечивает хранение id записей при переработке, очистку и обновлении GC, включая возврат остатков переработанных
|
||||
* страниц.
|
||||
*
|
||||
* При переработке GC записи преимущественно выбираются последовательно, но это не гарантируется. В LIFO-режиме
|
||||
* переработка и добавление записей в rkl происходит преимущественно в обратном порядке, но из-за завершения читающих
|
||||
* транзакций могут быть «скачки» в прямом направлении. В FIFO-режиме записи GC перерабатываются в прямом порядке и при
|
||||
* этом линейно, но не обязательно строго последовательно, при этом гарантируется что между добавляемыми в rkl
|
||||
* идентификаторами в GC нет записей, т.е. между первой (минимальный id) и последней (максимальный id) в GC нет записей
|
||||
* и весь интервал может быть использован для возврата остатков страниц в GC.
|
||||
*
|
||||
* Таким образом, комбинация линейного интервала и списка (отсортированного в порядке возрастания элементов) является
|
||||
* рациональным решением, близким к теоретически оптимальному пределу.
|
||||
*
|
||||
* Реализация rkl достаточно проста/прозрачная, если не считать неочевидную «магию» обмена непрерывного интервала и
|
||||
* образующихся в списке последовательностей. Однако, именно этот автоматически выполняемый без лишних операций обмен
|
||||
* оправдывает все накладные расходы. */
|
||||
typedef struct MDBX_rkl {
|
||||
txnid_t solid_begin, solid_end; /* начало и конец непрерывной последовательности solid_begin ... solid_end-1. */
|
||||
unsigned list_length; /* текущая длина списка. */
|
||||
unsigned list_limit; /* размер буфера выделенного под список, равен ARRAY_LENGTH(inplace) когда list == inplace. */
|
||||
txnid_t *list; /* список отдельных элементов в порядке возрастания (наименьший в начале). */
|
||||
txnid_t inplace[4 + 8]; /* статический массив для коротких списков, чтобы избавиться от выделения/освобождения памяти
|
||||
* в большинстве случаев. */
|
||||
} rkl_t;
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_init(rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_clear(rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_destroy(rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_destructive_move(rkl_t *dst, rkl_t *src);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL int __must_check_result rkl_copy(const rkl_t *src, rkl_t *dst);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool rkl_empty(const rkl_t *rkl) {
|
||||
return rkl->solid_begin > rkl->solid_end;
|
||||
}
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL bool rkl_check(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t rkl_len(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL bool rkl_contain(const rkl_t *rkl, txnid_t id);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL txnid_t rkl_lowest(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL txnid_t rkl_highest(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL int __must_check_result rkl_push(rkl_t *rkl, const txnid_t id,
|
||||
const bool known_continuous);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result txnid_t rkl_pop(rkl_t *rkl, const bool highest_not_lowest);
|
||||
|
||||
/* Итератор для rkl.
|
||||
* Обеспечивает изоляцию внутреннего устройства rkl от остального кода, чем существенно его упрощает.
|
||||
* Фактически именно использованием rkl с итераторами ликвидируется "ребус" исторически образовавшийся в gc-update. */
|
||||
typedef struct MDBX_rkl_iter {
|
||||
unsigned pos;
|
||||
unsigned solid_offset;
|
||||
} rkl_iterator_t;
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_iterator_init(rkl_iterator_t *iter, const rkl_t *rkl, const bool reverse);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result txnid_t rkl_iterator_turn(rkl_iterator_t *iter, const rkl_t *rkl,
|
||||
const bool reverse);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t rkl_iterator_left(rkl_iterator_t *iter,
|
||||
const rkl_t *rkl,
|
||||
const bool reverse);
|
Loading…
Reference in New Issue
Block a user