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/preface.h"
|
||||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/proto.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/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/sort.h"
|
||||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.c"
|
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.c"
|
||||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.h"
|
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.h"
|
||||||
@ -832,6 +834,8 @@ else()
|
|||||||
"${MDBX_SOURCE_DIR}/preface.h"
|
"${MDBX_SOURCE_DIR}/preface.h"
|
||||||
"${MDBX_SOURCE_DIR}/proto.h"
|
"${MDBX_SOURCE_DIR}/proto.h"
|
||||||
"${MDBX_SOURCE_DIR}/refund.c"
|
"${MDBX_SOURCE_DIR}/refund.c"
|
||||||
|
"${MDBX_SOURCE_DIR}/rkl.c"
|
||||||
|
"${MDBX_SOURCE_DIR}/rkl.h"
|
||||||
"${MDBX_SOURCE_DIR}/sort.h"
|
"${MDBX_SOURCE_DIR}/sort.h"
|
||||||
"${MDBX_SOURCE_DIR}/spill.c"
|
"${MDBX_SOURCE_DIR}/spill.c"
|
||||||
"${MDBX_SOURCE_DIR}/spill.h"
|
"${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 "essentials.h"/d' \
|
||||||
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
|
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
|
||||||
-e '/#include "proto.h"/r src/proto.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 "txl.h"/r src/txl.h' \
|
||||||
-e '/#include "unaligned.h"/r src/unaligned.h' \
|
-e '/#include "unaligned.h"/r src/unaligned.h' \
|
||||||
-e '/#include "cogs.h"/r src/cogs.h' \
|
-e '/#include "cogs.h"/r src/cogs.h' \
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include "page-ops.c"
|
#include "page-ops.c"
|
||||||
#include "pnl.c"
|
#include "pnl.c"
|
||||||
#include "refund.c"
|
#include "refund.c"
|
||||||
|
#include "rkl.c"
|
||||||
#include "spill.c"
|
#include "spill.c"
|
||||||
#include "table.c"
|
#include "table.c"
|
||||||
#include "tls.c"
|
#include "tls.c"
|
||||||
|
@ -46,6 +46,7 @@ typedef struct bind_reader_slot_result {
|
|||||||
|
|
||||||
#include "atomics-ops.h"
|
#include "atomics-ops.h"
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
|
#include "rkl.h"
|
||||||
#include "txl.h"
|
#include "txl.h"
|
||||||
#include "unaligned.h"
|
#include "unaligned.h"
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#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