libmdbx/src/gc-get.c

1461 lines
59 KiB
C
Raw Normal View History

/// \copyright SPDX-License-Identifier: Apache-2.0
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2024
#include "internals.h"
#if MDBX_ENABLE_MINCORE
/*------------------------------------------------------------------------------
* Проверка размещения/расположения отображенных страниц БД в ОЗУ (mem-in-core),
* с кешированием этой информации. */
static inline bool bit_tas(uint64_t *field, char bit) {
const uint64_t m = UINT64_C(1) << bit;
const bool r = (*field & m) != 0;
*field |= m;
return r;
}
static bool mincore_fetch(MDBX_env *const env, const size_t unit_begin) {
lck_t *const lck = env->lck;
for (size_t i = 1; i < ARRAY_LENGTH(lck->mincore_cache.begin); ++i) {
const ptrdiff_t dist = unit_begin - lck->mincore_cache.begin[i];
if (likely(dist >= 0 && dist < 64)) {
const pgno_t tmp_begin = lck->mincore_cache.begin[i];
const uint64_t tmp_mask = lck->mincore_cache.mask[i];
do {
lck->mincore_cache.begin[i] = lck->mincore_cache.begin[i - 1];
lck->mincore_cache.mask[i] = lck->mincore_cache.mask[i - 1];
} while (--i);
lck->mincore_cache.begin[0] = tmp_begin;
lck->mincore_cache.mask[0] = tmp_mask;
return bit_tas(lck->mincore_cache.mask, (char)dist);
}
}
size_t pages = 64;
unsigned unit_log = globals.sys_pagesize_ln2;
unsigned shift = 0;
if (env->ps > globals.sys_pagesize) {
unit_log = env->ps2ln;
shift = env->ps2ln - globals.sys_pagesize_ln2;
pages <<= shift;
}
const size_t offset = unit_begin << unit_log;
size_t length = pages << globals.sys_pagesize_ln2;
if (offset + length > env->dxb_mmap.current) {
length = env->dxb_mmap.current - offset;
pages = length >> globals.sys_pagesize_ln2;
}
#if MDBX_ENABLE_PGOP_STAT
env->lck->pgops.mincore.weak += 1;
#endif /* MDBX_ENABLE_PGOP_STAT */
uint8_t *const vector = alloca(pages);
if (unlikely(mincore(ptr_disp(env->dxb_mmap.base, offset), length,
(void *)vector))) {
NOTICE("mincore(+%zu, %zu), err %d", offset, length, errno);
return false;
}
for (size_t i = 1; i < ARRAY_LENGTH(lck->mincore_cache.begin); ++i) {
lck->mincore_cache.begin[i] = lck->mincore_cache.begin[i - 1];
lck->mincore_cache.mask[i] = lck->mincore_cache.mask[i - 1];
}
lck->mincore_cache.begin[0] = unit_begin;
uint64_t mask = 0;
#ifdef MINCORE_INCORE
STATIC_ASSERT(MINCORE_INCORE == 1);
#endif
for (size_t i = 0; i < pages; ++i) {
uint64_t bit = (vector[i] & 1) == 0;
bit <<= i >> shift;
mask |= bit;
}
lck->mincore_cache.mask[0] = ~mask;
return bit_tas(lck->mincore_cache.mask, 0);
}
#endif /* MDBX_ENABLE_MINCORE */
MDBX_MAYBE_UNUSED static inline bool mincore_probe(MDBX_env *const env,
const pgno_t pgno) {
#if MDBX_ENABLE_MINCORE
const size_t offset_aligned =
floor_powerof2(pgno2bytes(env, pgno), globals.sys_pagesize);
const unsigned unit_log2 = (env->ps2ln > globals.sys_pagesize_ln2)
? env->ps2ln
: globals.sys_pagesize_ln2;
const size_t unit_begin = offset_aligned >> unit_log2;
eASSERT(env, (unit_begin << unit_log2) == offset_aligned);
const ptrdiff_t dist = unit_begin - env->lck->mincore_cache.begin[0];
if (likely(dist >= 0 && dist < 64))
return bit_tas(env->lck->mincore_cache.mask, (char)dist);
return mincore_fetch(env, unit_begin);
#else
(void)env;
(void)pgno;
return false;
#endif /* MDBX_ENABLE_MINCORE */
}
/*----------------------------------------------------------------------------*/
MDBX_MAYBE_UNUSED __hot static pgno_t *
scan4seq_fallback(pgno_t *range, const size_t len, const size_t seq) {
assert(seq > 0 && len > seq);
#if MDBX_PNL_ASCENDING
assert(range[-1] == len);
const pgno_t *const detent = range + len - seq;
const ptrdiff_t offset = (ptrdiff_t)seq;
const pgno_t target = (pgno_t)offset;
if (likely(len > seq + 3)) {
do {
const pgno_t diff0 = range[offset + 0] - range[0];
const pgno_t diff1 = range[offset + 1] - range[1];
const pgno_t diff2 = range[offset + 2] - range[2];
const pgno_t diff3 = range[offset + 3] - range[3];
if (diff0 == target)
return range + 0;
if (diff1 == target)
return range + 1;
if (diff2 == target)
return range + 2;
if (diff3 == target)
return range + 3;
range += 4;
} while (range + 3 < detent);
if (range == detent)
return nullptr;
}
do
if (range[offset] - *range == target)
return range;
while (++range < detent);
#else
assert(range[-(ptrdiff_t)len] == len);
const pgno_t *const detent = range - len + seq;
const ptrdiff_t offset = -(ptrdiff_t)seq;
const pgno_t target = (pgno_t)offset;
if (likely(len > seq + 3)) {
do {
const pgno_t diff0 = range[-0] - range[offset - 0];
const pgno_t diff1 = range[-1] - range[offset - 1];
const pgno_t diff2 = range[-2] - range[offset - 2];
const pgno_t diff3 = range[-3] - range[offset - 3];
/* Смысл вычислений до ветвлений в том, чтобы позволить компилятору
* загружать и вычислять все значения параллельно. */
if (diff0 == target)
return range - 0;
if (diff1 == target)
return range - 1;
if (diff2 == target)
return range - 2;
if (diff3 == target)
return range - 3;
range -= 4;
} while (range > detent + 3);
if (range == detent)
return nullptr;
}
do
if (*range - range[offset] == target)
return range;
while (--range > detent);
#endif /* pnl_t sort-order */
return nullptr;
}
MDBX_MAYBE_UNUSED static const pgno_t *scan4range_checker(const pnl_t pnl,
const size_t seq) {
size_t begin = MDBX_PNL_ASCENDING ? 1 : MDBX_PNL_GETSIZE(pnl);
#if MDBX_PNL_ASCENDING
while (seq <= MDBX_PNL_GETSIZE(pnl) - begin) {
if (pnl[begin + seq] - pnl[begin] == seq)
return pnl + begin;
++begin;
}
#else
while (begin > seq) {
if (pnl[begin - seq] - pnl[begin] == seq)
return pnl + begin;
--begin;
}
#endif /* pnl_t sort-order */
return nullptr;
}
#if defined(_MSC_VER) && !defined(__builtin_clz) && \
!__has_builtin(__builtin_clz)
MDBX_MAYBE_UNUSED static __always_inline size_t __builtin_clz(uint32_t value) {
unsigned long index;
_BitScanReverse(&index, value);
return 31 - index;
}
#endif /* _MSC_VER */
#if defined(_MSC_VER) && !defined(__builtin_clzl) && \
!__has_builtin(__builtin_clzl)
MDBX_MAYBE_UNUSED static __always_inline size_t __builtin_clzl(size_t value) {
unsigned long index;
#ifdef _WIN64
assert(sizeof(value) == 8);
_BitScanReverse64(&index, value);
return 63 - index;
#else
assert(sizeof(value) == 4);
_BitScanReverse(&index, value);
return 31 - index;
#endif
}
#endif /* _MSC_VER */
#if !MDBX_PNL_ASCENDING
#if !defined(MDBX_ATTRIBUTE_TARGET) && \
(__has_attribute(__target__) || __GNUC_PREREQ(5, 0))
#define MDBX_ATTRIBUTE_TARGET(target) __attribute__((__target__(target)))
#endif /* MDBX_ATTRIBUTE_TARGET */
#ifndef MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND
/* Workaround for GCC's bug with `-m32 -march=i686 -Ofast`
* gcc/i686-buildroot-linux-gnu/12.2.0/include/xmmintrin.h:814:1:
* error: inlining failed in call to 'always_inline' '_mm_movemask_ps':
* target specific option mismatch */
#if !defined(__FAST_MATH__) || !__FAST_MATH__ || !defined(__GNUC__) || \
defined(__e2k__) || defined(__clang__) || defined(__amd64__) || \
defined(__SSE2__)
#define MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND 0
#else
#define MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND 1
#endif
#endif /* MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND */
#if defined(__SSE2__) && defined(__SSE__)
#define MDBX_ATTRIBUTE_TARGET_SSE2 /* nope */
#elif (defined(_M_IX86_FP) && _M_IX86_FP >= 2) || defined(__amd64__)
#define __SSE2__
#define MDBX_ATTRIBUTE_TARGET_SSE2 /* nope */
#elif defined(MDBX_ATTRIBUTE_TARGET) && defined(__ia32__) && \
!MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND
#define MDBX_ATTRIBUTE_TARGET_SSE2 MDBX_ATTRIBUTE_TARGET("sse,sse2")
#endif /* __SSE2__ */
#if defined(__AVX2__)
#define MDBX_ATTRIBUTE_TARGET_AVX2 /* nope */
#elif defined(MDBX_ATTRIBUTE_TARGET) && defined(__ia32__) && \
!MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND
#define MDBX_ATTRIBUTE_TARGET_AVX2 MDBX_ATTRIBUTE_TARGET("sse,sse2,avx,avx2")
#endif /* __AVX2__ */
#if defined(MDBX_ATTRIBUTE_TARGET_AVX2)
#if defined(__AVX512BW__)
#define MDBX_ATTRIBUTE_TARGET_AVX512BW /* nope */
#elif defined(MDBX_ATTRIBUTE_TARGET) && defined(__ia32__) && \
!MDBX_GCC_FASTMATH_i686_SIMD_WORKAROUND && \
(__GNUC_PREREQ(6, 0) || __CLANG_PREREQ(5, 0))
#define MDBX_ATTRIBUTE_TARGET_AVX512BW \
MDBX_ATTRIBUTE_TARGET("sse,sse2,avx,avx2,avx512bw")
#endif /* __AVX512BW__ */
#endif /* MDBX_ATTRIBUTE_TARGET_AVX2 for MDBX_ATTRIBUTE_TARGET_AVX512BW */
#ifdef MDBX_ATTRIBUTE_TARGET_SSE2
MDBX_ATTRIBUTE_TARGET_SSE2 static __always_inline unsigned
diffcmp2mask_sse2(const pgno_t *const ptr, const ptrdiff_t offset,
const __m128i pattern) {
const __m128i f = _mm_loadu_si128((const __m128i *)ptr);
const __m128i l = _mm_loadu_si128((const __m128i *)(ptr + offset));
const __m128i cmp = _mm_cmpeq_epi32(_mm_sub_epi32(f, l), pattern);
return _mm_movemask_ps(*(const __m128 *)&cmp);
}
MDBX_MAYBE_UNUSED __hot MDBX_ATTRIBUTE_TARGET_SSE2 static pgno_t *
scan4seq_sse2(pgno_t *range, const size_t len, const size_t seq) {
assert(seq > 0 && len > seq);
#if MDBX_PNL_ASCENDING
#error "FIXME: Not implemented"
#endif /* MDBX_PNL_ASCENDING */
assert(range[-(ptrdiff_t)len] == len);
pgno_t *const detent = range - len + seq;
const ptrdiff_t offset = -(ptrdiff_t)seq;
const pgno_t target = (pgno_t)offset;
const __m128i pattern = _mm_set1_epi32(target);
uint8_t mask;
if (likely(len > seq + 3)) {
do {
mask = (uint8_t)diffcmp2mask_sse2(range - 3, offset, pattern);
if (mask) {
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
found:
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
return range + 28 - __builtin_clz(mask);
}
range -= 4;
} while (range > detent + 3);
if (range == detent)
return nullptr;
}
/* Далее происходит чтение от 4 до 12 лишних байт, которые могут быть не
* только за пределами региона выделенного под PNL, но и пересекать границу
* страницы памяти. Что может приводить как к ошибкам ASAN, так и к падению.
* Поэтому проверяем смещение на странице, а с ASAN всегда страхуемся. */
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
const unsigned on_page_safe_mask = 0xff0 /* enough for '-15' bytes offset */;
if (likely(on_page_safe_mask & (uintptr_t)(range + offset)) &&
!RUNNING_ON_VALGRIND) {
const unsigned extra = (unsigned)(detent + 4 - range);
assert(extra > 0 && extra < 4);
mask = 0xF << extra;
mask &= diffcmp2mask_sse2(range - 3, offset, pattern);
if (mask)
goto found;
return nullptr;
}
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
do
if (*range - range[offset] == target)
return range;
while (--range != detent);
return nullptr;
}
#endif /* MDBX_ATTRIBUTE_TARGET_SSE2 */
#ifdef MDBX_ATTRIBUTE_TARGET_AVX2
MDBX_ATTRIBUTE_TARGET_AVX2 static __always_inline unsigned
diffcmp2mask_avx2(const pgno_t *const ptr, const ptrdiff_t offset,
const __m256i pattern) {
const __m256i f = _mm256_loadu_si256((const __m256i *)ptr);
const __m256i l = _mm256_loadu_si256((const __m256i *)(ptr + offset));
const __m256i cmp = _mm256_cmpeq_epi32(_mm256_sub_epi32(f, l), pattern);
return _mm256_movemask_ps(*(const __m256 *)&cmp);
}
MDBX_ATTRIBUTE_TARGET_AVX2 static __always_inline unsigned
diffcmp2mask_sse2avx(const pgno_t *const ptr, const ptrdiff_t offset,
const __m128i pattern) {
const __m128i f = _mm_loadu_si128((const __m128i *)ptr);
const __m128i l = _mm_loadu_si128((const __m128i *)(ptr + offset));
const __m128i cmp = _mm_cmpeq_epi32(_mm_sub_epi32(f, l), pattern);
return _mm_movemask_ps(*(const __m128 *)&cmp);
}
MDBX_MAYBE_UNUSED __hot MDBX_ATTRIBUTE_TARGET_AVX2 static pgno_t *
scan4seq_avx2(pgno_t *range, const size_t len, const size_t seq) {
assert(seq > 0 && len > seq);
#if MDBX_PNL_ASCENDING
#error "FIXME: Not implemented"
#endif /* MDBX_PNL_ASCENDING */
assert(range[-(ptrdiff_t)len] == len);
pgno_t *const detent = range - len + seq;
const ptrdiff_t offset = -(ptrdiff_t)seq;
const pgno_t target = (pgno_t)offset;
const __m256i pattern = _mm256_set1_epi32(target);
uint8_t mask;
if (likely(len > seq + 7)) {
do {
mask = (uint8_t)diffcmp2mask_avx2(range - 7, offset, pattern);
if (mask) {
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
found:
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
return range + 24 - __builtin_clz(mask);
}
range -= 8;
} while (range > detent + 7);
if (range == detent)
return nullptr;
}
/* Далее происходит чтение от 4 до 28 лишних байт, которые могут быть не
* только за пределами региона выделенного под PNL, но и пересекать границу
* страницы памяти. Что может приводить как к ошибкам ASAN, так и к падению.
* Поэтому проверяем смещение на странице, а с ASAN всегда страхуемся. */
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
const unsigned on_page_safe_mask = 0xfe0 /* enough for '-31' bytes offset */;
if (likely(on_page_safe_mask & (uintptr_t)(range + offset)) &&
!RUNNING_ON_VALGRIND) {
const unsigned extra = (unsigned)(detent + 8 - range);
assert(extra > 0 && extra < 8);
mask = 0xFF << extra;
mask &= diffcmp2mask_avx2(range - 7, offset, pattern);
if (mask)
goto found;
return nullptr;
}
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
if (range - 3 > detent) {
mask = diffcmp2mask_sse2avx(range - 3, offset, *(const __m128i *)&pattern);
if (mask)
return range + 28 - __builtin_clz(mask);
range -= 4;
}
while (range > detent) {
if (*range - range[offset] == target)
return range;
--range;
}
return nullptr;
}
#endif /* MDBX_ATTRIBUTE_TARGET_AVX2 */
#ifdef MDBX_ATTRIBUTE_TARGET_AVX512BW
MDBX_ATTRIBUTE_TARGET_AVX512BW static __always_inline unsigned
diffcmp2mask_avx512bw(const pgno_t *const ptr, const ptrdiff_t offset,
const __m512i pattern) {
const __m512i f = _mm512_loadu_si512((const __m512i *)ptr);
const __m512i l = _mm512_loadu_si512((const __m512i *)(ptr + offset));
return _mm512_cmpeq_epi32_mask(_mm512_sub_epi32(f, l), pattern);
}
MDBX_MAYBE_UNUSED __hot MDBX_ATTRIBUTE_TARGET_AVX512BW static pgno_t *
scan4seq_avx512bw(pgno_t *range, const size_t len, const size_t seq) {
assert(seq > 0 && len > seq);
#if MDBX_PNL_ASCENDING
#error "FIXME: Not implemented"
#endif /* MDBX_PNL_ASCENDING */
assert(range[-(ptrdiff_t)len] == len);
pgno_t *const detent = range - len + seq;
const ptrdiff_t offset = -(ptrdiff_t)seq;
const pgno_t target = (pgno_t)offset;
const __m512i pattern = _mm512_set1_epi32(target);
unsigned mask;
if (likely(len > seq + 15)) {
do {
mask = diffcmp2mask_avx512bw(range - 15, offset, pattern);
if (mask) {
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
found:
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
return range + 16 - __builtin_clz(mask);
}
range -= 16;
} while (range > detent + 15);
if (range == detent)
return nullptr;
}
/* Далее происходит чтение от 4 до 60 лишних байт, которые могут быть не
* только за пределами региона выделенного под PNL, но и пересекать границу
* страницы памяти. Что может приводить как к ошибкам ASAN, так и к падению.
* Поэтому проверяем смещение на странице, а с ASAN всегда страхуемся. */
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
const unsigned on_page_safe_mask = 0xfc0 /* enough for '-63' bytes offset */;
if (likely(on_page_safe_mask & (uintptr_t)(range + offset)) &&
!RUNNING_ON_VALGRIND) {
const unsigned extra = (unsigned)(detent + 16 - range);
assert(extra > 0 && extra < 16);
mask = 0xFFFF << extra;
mask &= diffcmp2mask_avx512bw(range - 15, offset, pattern);
if (mask)
goto found;
return nullptr;
}
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
if (range - 7 > detent) {
mask = diffcmp2mask_avx2(range - 7, offset, *(const __m256i *)&pattern);
if (mask)
return range + 24 - __builtin_clz(mask);
range -= 8;
}
if (range - 3 > detent) {
mask = diffcmp2mask_sse2avx(range - 3, offset, *(const __m128i *)&pattern);
if (mask)
return range + 28 - __builtin_clz(mask);
range -= 4;
}
while (range > detent) {
if (*range - range[offset] == target)
return range;
--range;
}
return nullptr;
}
#endif /* MDBX_ATTRIBUTE_TARGET_AVX512BW */
#if (defined(__ARM_NEON) || defined(__ARM_NEON__)) && \
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
static __always_inline size_t diffcmp2mask_neon(const pgno_t *const ptr,
const ptrdiff_t offset,
const uint32x4_t pattern) {
const uint32x4_t f = vld1q_u32(ptr);
const uint32x4_t l = vld1q_u32(ptr + offset);
const uint16x4_t cmp = vmovn_u32(vceqq_u32(vsubq_u32(f, l), pattern));
if (sizeof(size_t) > 7)
return vget_lane_u64(vreinterpret_u64_u16(cmp), 0);
else
return vget_lane_u32(vreinterpret_u32_u8(vmovn_u16(vcombine_u16(cmp, cmp))),
0);
}
__hot static pgno_t *scan4seq_neon(pgno_t *range, const size_t len,
const size_t seq) {
assert(seq > 0 && len > seq);
#if MDBX_PNL_ASCENDING
#error "FIXME: Not implemented"
#endif /* MDBX_PNL_ASCENDING */
assert(range[-(ptrdiff_t)len] == len);
pgno_t *const detent = range - len + seq;
const ptrdiff_t offset = -(ptrdiff_t)seq;
const pgno_t target = (pgno_t)offset;
const uint32x4_t pattern = vmovq_n_u32(target);
size_t mask;
if (likely(len > seq + 3)) {
do {
mask = diffcmp2mask_neon(range - 3, offset, pattern);
if (mask) {
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
found:
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
return ptr_disp(range, -(__builtin_clzl(mask) >> sizeof(size_t) / 4));
}
range -= 4;
} while (range > detent + 3);
if (range == detent)
return nullptr;
}
/* Далее происходит чтение от 4 до 12 лишних байт, которые могут быть не
* только за пределами региона выделенного под PNL, но и пересекать границу
* страницы памяти. Что может приводить как к ошибкам ASAN, так и к падению.
* Поэтому проверяем смещение на странице, а с ASAN всегда страхуемся. */
#if !defined(ENABLE_MEMCHECK) && !defined(__SANITIZE_ADDRESS__)
const unsigned on_page_safe_mask = 0xff0 /* enough for '-15' bytes offset */;
if (likely(on_page_safe_mask & (uintptr_t)(range + offset)) &&
!RUNNING_ON_VALGRIND) {
const unsigned extra = (unsigned)(detent + 4 - range);
assert(extra > 0 && extra < 4);
mask = (~(size_t)0) << (extra * sizeof(size_t) * 2);
mask &= diffcmp2mask_neon(range - 3, offset, pattern);
if (mask)
goto found;
return nullptr;
}
#endif /* !ENABLE_MEMCHECK && !__SANITIZE_ADDRESS__ */
do
if (*range - range[offset] == target)
return range;
while (--range != detent);
return nullptr;
}
#endif /* __ARM_NEON || __ARM_NEON__ */
#if defined(__AVX512BW__) && defined(MDBX_ATTRIBUTE_TARGET_AVX512BW)
#define scan4seq_default scan4seq_avx512bw
#define scan4seq_impl scan4seq_default
#elif defined(__AVX2__) && defined(MDBX_ATTRIBUTE_TARGET_AVX2)
#define scan4seq_default scan4seq_avx2
#elif defined(__SSE2__) && defined(MDBX_ATTRIBUTE_TARGET_SSE2)
#define scan4seq_default scan4seq_sse2
#elif (defined(__ARM_NEON) || defined(__ARM_NEON__)) && \
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define scan4seq_default scan4seq_neon
/* Choosing of another variants should be added here. */
#endif /* scan4seq_default */
#endif /* MDBX_PNL_ASCENDING */
#ifndef scan4seq_default
#define scan4seq_default scan4seq_fallback
#endif /* scan4seq_default */
#ifdef scan4seq_impl
/* The scan4seq_impl() is the best or no alternatives */
#elif !MDBX_HAVE_BUILTIN_CPU_SUPPORTS
/* The scan4seq_default() will be used since no cpu-features detection support
* from compiler. Please don't ask to implement cpuid-based detection and don't
* make such PRs. */
#define scan4seq_impl scan4seq_default
#else
/* Selecting the most appropriate implementation at runtime,
* depending on the available CPU features. */
static pgno_t *scan4seq_resolver(pgno_t *range, const size_t len,
const size_t seq);
static pgno_t *(*scan4seq_impl)(pgno_t *range, const size_t len,
const size_t seq) = scan4seq_resolver;
static pgno_t *scan4seq_resolver(pgno_t *range, const size_t len,
const size_t seq) {
pgno_t *(*choice)(pgno_t *range, const size_t len, const size_t seq) =
nullptr;
#if __has_builtin(__builtin_cpu_init) || defined(__BUILTIN_CPU_INIT__) || \
__GNUC_PREREQ(4, 8)
__builtin_cpu_init();
#endif /* __builtin_cpu_init() */
#ifdef MDBX_ATTRIBUTE_TARGET_SSE2
if (__builtin_cpu_supports("sse2"))
choice = scan4seq_sse2;
#endif /* MDBX_ATTRIBUTE_TARGET_SSE2 */
#ifdef MDBX_ATTRIBUTE_TARGET_AVX2
if (__builtin_cpu_supports("avx2"))
choice = scan4seq_avx2;
#endif /* MDBX_ATTRIBUTE_TARGET_AVX2 */
#ifdef MDBX_ATTRIBUTE_TARGET_AVX512BW
if (__builtin_cpu_supports("avx512bw"))
choice = scan4seq_avx512bw;
#endif /* MDBX_ATTRIBUTE_TARGET_AVX512BW */
/* Choosing of another variants should be added here. */
scan4seq_impl = choice ? choice : scan4seq_default;
return scan4seq_impl(range, len, seq);
}
#endif /* scan4seq_impl */
/*----------------------------------------------------------------------------*/
#define ALLOC_COALESCE 4 /* внутреннее состояние */
#define ALLOC_SHOULD_SCAN 8 /* внутреннее состояние */
#define ALLOC_LIFO 16 /* внутреннее состояние */
static inline bool is_gc_usable(MDBX_txn *txn, const MDBX_cursor *mc,
const uint8_t flags) {
/* If txn is updating the GC, then the retired-list cannot play catch-up with
* itself by growing while trying to save it. */
if (mc->tree == &txn->dbs[FREE_DBI] && !(flags & ALLOC_RESERVE) &&
!(mc->flags & z_gcu_preparation))
return false;
/* avoid search inside empty tree and while tree is updating,
https://libmdbx.dqdkfa.ru/dead-github/issues/31 */
if (unlikely(txn->dbs[FREE_DBI].items == 0)) {
txn->flags |= txn_gc_drained;
return false;
}
return true;
}
__hot static bool is_already_reclaimed(const MDBX_txn *txn, txnid_t id) {
const size_t len = MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed);
for (size_t i = 1; i <= len; ++i)
if (txn->tw.gc.reclaimed[i] == id)
return true;
return false;
}
__hot static pgno_t relist_get_single(MDBX_txn *txn) {
const size_t len = MDBX_PNL_GETSIZE(txn->tw.relist);
assert(len > 0);
pgno_t *target = MDBX_PNL_EDGE(txn->tw.relist);
const ptrdiff_t dir = MDBX_PNL_ASCENDING ? 1 : -1;
/* Есть ТРИ потенциально выигрышные, но противо-направленные тактики:
*
* 1. Стараться использовать страницы с наименьшими номерами. Так обмен с
* диском будет более кучным, а у страниц ближе к концу БД будет больше шансов
* попасть под авто-компактификацию. Частично эта тактика уже реализована, но
* для её эффективности требуется явно приоритезировать выделение страниц:
* - поддерживать для relist, для ближних и для дальних страниц;
* - использовать страницы из дальнего списка, если первый пуст,
* а второй слишком большой, либо при пустой GC.
*
* 2. Стараться выделять страницы последовательно. Так записываемые на диск
* регионы будут линейными, что принципиально ускоряет запись на HDD.
* Одновременно, в среднем это не повлияет на чтение, точнее говоря, если
* порядок чтения не совпадает с порядком изменения (иначе говоря, если
* чтение не коррклирует с обновлениями и/или вставками) то не повлияет, иначе
* может ускорить. Однако, последовательности в среднем достаточно редки.
* Поэтому для эффективности требуется аккумулировать и поддерживать в ОЗУ
* огромные списки страниц, а затем сохранять их обратно в БД. Текущий формат
* БД (без битовых карт) для этого крайне не удачен. Поэтому эта тактика не
* имеет шансов быть успешной без смены формата БД (Mithril).
*
* 3. Стараться экономить последовательности страниц. Это позволяет избегать
* лишнего чтения/поиска в GC при более-менее постоянном размещении и/или
* обновлении данных требующих более одной страницы. Проблема в том, что без
* информации от приложения библиотека не может знать насколько
* востребованными будут последовательности в ближайшей перспективе, а
* экономия последовательностей "на всякий случай" не только затратна
* сама-по-себе, но и работает во вред.
*
* Поэтому:
* - в TODO добавляется разделение relist на «ближние» и «дальние» страницы,
* с последующей реализацией первой тактики;
* - преимущественное использование последовательностей отправляется
* в MithrilDB как составляющая "HDD frendly" feature;
* - реализованная в 3757eb72f7c6b46862f8f17881ac88e8cecc1979 экономия
* последовательностей отключается через MDBX_ENABLE_SAVING_SEQUENCES=0.
*
* В качестве альтернативы для безусловной «экономии» последовательностей,
* в следующих версиях libmdbx, вероятно, будет предложено
* API для взаимодействия с GC:
* - получение размера GC, включая гистограммы размеров последовательностей
* и близости к концу БД;
* - включение формирования "линейного запаса" для последующего использования
* в рамках текущей транзакции;
* - намеренная загрузка GC в память для коагуляции и "выпрямления";
* - намеренное копирование данных из страниц в конце БД для последующего
* из освобождения, т.е. контролируемая компактификация по запросу. */
#ifndef MDBX_ENABLE_SAVING_SEQUENCES
#define MDBX_ENABLE_SAVING_SEQUENCES 0
#endif
if (MDBX_ENABLE_SAVING_SEQUENCES && unlikely(target[dir] == *target + 1) &&
len > 2) {
/* Пытаемся пропускать последовательности при наличии одиночных элементов.
* TODO: необходимо кэшировать пропускаемые последовательности
* чтобы не сканировать список сначала при каждом выделении. */
pgno_t *scan = target + dir + dir;
size_t left = len;
do {
if (likely(scan[-dir] != *scan - 1 && *scan + 1 != scan[dir])) {
#if MDBX_PNL_ASCENDING
target = scan;
break;
#else
/* вырезаем элемент с перемещением хвоста */
const pgno_t pgno = *scan;
MDBX_PNL_SETSIZE(txn->tw.relist, len - 1);
while (++scan <= target)
scan[-1] = *scan;
return pgno;
#endif
}
scan += dir;
} while (--left > 2);
}
const pgno_t pgno = *target;
#if MDBX_PNL_ASCENDING
/* вырезаем элемент с перемещением хвоста */
MDBX_PNL_SETSIZE(txn->tw.relist, len - 1);
for (const pgno_t *const end = txn->tw.relist + len - 1; target <= end;
++target)
*target = target[1];
#else
/* перемещать хвост не нужно, просто усекам список */
MDBX_PNL_SETSIZE(txn->tw.relist, len - 1);
#endif
return pgno;
}
__hot static pgno_t relist_get_sequence(MDBX_txn *txn, const size_t num,
uint8_t flags) {
const size_t len = MDBX_PNL_GETSIZE(txn->tw.relist);
pgno_t *edge = MDBX_PNL_EDGE(txn->tw.relist);
assert(len >= num && num > 1);
const size_t seq = num - 1;
#if !MDBX_PNL_ASCENDING
if (edge[-(ptrdiff_t)seq] - *edge == seq) {
if (unlikely(flags & ALLOC_RESERVE))
return P_INVALID;
assert(edge == scan4range_checker(txn->tw.relist, seq));
/* перемещать хвост не нужно, просто усекам список */
MDBX_PNL_SETSIZE(txn->tw.relist, len - num);
return *edge;
}
#endif
pgno_t *target = scan4seq_impl(edge, len, seq);
assert(target == scan4range_checker(txn->tw.relist, seq));
if (target) {
if (unlikely(flags & ALLOC_RESERVE))
return P_INVALID;
const pgno_t pgno = *target;
/* вырезаем найденную последовательность с перемещением хвоста */
MDBX_PNL_SETSIZE(txn->tw.relist, len - num);
#if MDBX_PNL_ASCENDING
for (const pgno_t *const end = txn->tw.relist + len - num; target <= end;
++target)
*target = target[num];
#else
for (const pgno_t *const end = txn->tw.relist + len; ++target <= end;)
target[-(ptrdiff_t)num] = *target;
#endif
return pgno;
}
return 0;
}
static inline pgr_t page_alloc_finalize(MDBX_env *const env,
MDBX_txn *const txn,
const MDBX_cursor *const mc,
const pgno_t pgno, const size_t num) {
#if MDBX_ENABLE_PROFGC
size_t majflt_before;
const uint64_t cputime_before = osal_cputime(&majflt_before);
gc_prof_stat_t *const prof = (mc->mc_dbi == FREE_DBI)
? &env->lck->pgops.gc_prof.self
: &env->lck->pgops.gc_prof.work;
#else
(void)mc;
#endif /* MDBX_ENABLE_PROFGC */
ENSURE(env, pgno >= NUM_METAS);
pgr_t ret;
bool need_clean = (env->flags & MDBX_PAGEPERTURB) != 0;
if (env->flags & MDBX_WRITEMAP) {
ret.page = pgno2page(env, pgno);
MDBX_ASAN_UNPOISON_MEMORY_REGION(ret.page, pgno2bytes(env, num));
VALGRIND_MAKE_MEM_UNDEFINED(ret.page, pgno2bytes(env, num));
/* Содержимое выделенной страницы не нужно, но если страница отсутствует
* в ОЗУ (что весьма вероятно), то любое обращение к ней приведет
* к page-fault:
* - прерыванию по отсутствию страницы;
* - переключение контекста в режим ядра с засыпанием процесса;
* - чтение страницы с диска;
* - обновление PTE и пробуждением процесса;
* - переключение контекста по доступности ЦПУ.
*
* Пытаемся минимизировать накладные расходы записывая страницу, что при
* наличии unified page cache приведет к появлению страницы в ОЗУ без чтения
* с диска. При этом запись на диск должна быть отложена адекватным ядром,
* так как страница отображена в память в режиме чтения-записи и следом в
* неё пишет ЦПУ. */
/* В случае если страница в памяти процесса, то излишняя запись может быть
* достаточно дорогой. Кроме системного вызова и копирования данных, в особо
* одаренных ОС при этом могут включаться файловая система, выделяться
* временная страница, пополняться очереди асинхронного выполнения,
* обновляться PTE с последующей генерацией page-fault и чтением данных из
* грязной I/O очереди. Из-за этого штраф за лишнюю запись может быть
* сравним с избегаемым ненужным чтением. */
if (env->prefault_write_activated) {
void *const pattern =
ptr_disp(env->page_auxbuf, need_clean ? env->ps : env->ps * 2);
size_t file_offset = pgno2bytes(env, pgno);
if (likely(num == 1)) {
if (!mincore_probe(env, pgno)) {
osal_pwrite(env->lazy_fd, pattern, env->ps, file_offset);
#if MDBX_ENABLE_PGOP_STAT
env->lck->pgops.prefault.weak += 1;
#endif /* MDBX_ENABLE_PGOP_STAT */
need_clean = false;
}
} else {
struct iovec iov[MDBX_AUXILARY_IOV_MAX];
size_t n = 0, cleared = 0;
for (size_t i = 0; i < num; ++i) {
if (!mincore_probe(env, pgno + (pgno_t)i)) {
++cleared;
iov[n].iov_len = env->ps;
iov[n].iov_base = pattern;
if (unlikely(++n == MDBX_AUXILARY_IOV_MAX)) {
osal_pwritev(env->lazy_fd, iov, MDBX_AUXILARY_IOV_MAX,
file_offset);
#if MDBX_ENABLE_PGOP_STAT
env->lck->pgops.prefault.weak += 1;
#endif /* MDBX_ENABLE_PGOP_STAT */
file_offset += pgno2bytes(env, MDBX_AUXILARY_IOV_MAX);
n = 0;
}
}
}
if (likely(n > 0)) {
osal_pwritev(env->lazy_fd, iov, n, file_offset);
#if MDBX_ENABLE_PGOP_STAT
env->lck->pgops.prefault.weak += 1;
#endif /* MDBX_ENABLE_PGOP_STAT */
}
if (cleared == num)
need_clean = false;
}
}
} else {
ret.page = page_shadow_alloc(txn, num);
if (unlikely(!ret.page)) {
ret.err = MDBX_ENOMEM;
goto bailout;
}
}
if (unlikely(need_clean))
memset(ret.page, -1, pgno2bytes(env, num));
VALGRIND_MAKE_MEM_UNDEFINED(ret.page, pgno2bytes(env, num));
ret.page->pgno = pgno;
ret.page->dupfix_ksize = 0;
ret.page->flags = 0;
if ((ASSERT_ENABLED() || AUDIT_ENABLED()) && num > 1) {
ret.page->pages = (pgno_t)num;
ret.page->flags = P_LARGE;
}
ret.err = page_dirty(txn, ret.page, (pgno_t)num);
bailout:
tASSERT(txn, pnl_check_allocated(txn->tw.relist, txn->geo.first_unallocated -
MDBX_ENABLE_REFUND));
#if MDBX_ENABLE_PROFGC
size_t majflt_after;
prof->xtime_cpu += osal_cputime(&majflt_after) - cputime_before;
prof->majflt += (uint32_t)(majflt_after - majflt_before);
#endif /* MDBX_ENABLE_PROFGC */
return ret;
}
pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num,
uint8_t flags) {
pgr_t ret;
MDBX_txn *const txn = mc->txn;
MDBX_env *const env = txn->env;
#if MDBX_ENABLE_PROFGC
gc_prof_stat_t *const prof = (mc->mc_dbi == FREE_DBI)
? &env->lck->pgops.gc_prof.self
: &env->lck->pgops.gc_prof.work;
prof->spe_counter += 1;
#endif /* MDBX_ENABLE_PROFGC */
eASSERT(env, num > 0 || (flags & ALLOC_RESERVE));
eASSERT(env, pnl_check_allocated(txn->tw.relist, txn->geo.first_unallocated -
MDBX_ENABLE_REFUND));
size_t newnext;
const uint64_t monotime_begin =
(MDBX_ENABLE_PROFGC || (num > 1 && env->options.gc_time_limit))
? osal_monotime()
: 0;
struct monotime_cache now_cache;
now_cache.expire_countdown =
1 /* старт с 1 позволяет избавиться как от лишних системных вызовов когда
лимит времени задан нулевой или уже исчерпан, так и от подсчета
времени при не-достижении rp_augment_limit */
;
now_cache.value = monotime_begin;
pgno_t pgno = 0;
if (num > 1) {
#if MDBX_ENABLE_PROFGC
prof->xpages += 1;
#endif /* MDBX_ENABLE_PROFGC */
if (MDBX_PNL_GETSIZE(txn->tw.relist) >= num) {
eASSERT(env,
MDBX_PNL_LAST(txn->tw.relist) < txn->geo.first_unallocated &&
MDBX_PNL_FIRST(txn->tw.relist) < txn->geo.first_unallocated);
pgno = relist_get_sequence(txn, num, flags);
if (likely(pgno))
goto done;
}
} else {
eASSERT(env, num == 0 || MDBX_PNL_GETSIZE(txn->tw.relist) == 0);
eASSERT(env, !(flags & ALLOC_RESERVE) || num == 0);
}
//---------------------------------------------------------------------------
if (unlikely(!is_gc_usable(txn, mc, flags))) {
eASSERT(env, (txn->flags & txn_gc_drained) || num > 1);
goto no_gc;
}
eASSERT(env,
(flags & (ALLOC_COALESCE | ALLOC_LIFO | ALLOC_SHOULD_SCAN)) == 0);
flags += (env->flags & MDBX_LIFORECLAIM) ? ALLOC_LIFO : 0;
if (/* Не коагулируем записи при подготовке резерва для обновления GC.
* Иначе попытка увеличить резерв может приводить к необходимости ещё
* большего резерва из-за увеличения списка переработанных страниц. */
(flags & ALLOC_RESERVE) == 0) {
if (txn->dbs[FREE_DBI].branch_pages &&
MDBX_PNL_GETSIZE(txn->tw.relist) < env->maxgc_large1page / 2)
flags += ALLOC_COALESCE;
}
MDBX_cursor *const gc = ptr_disp(env->basal_txn, sizeof(MDBX_txn));
eASSERT(env, mc != gc && gc->next == gc);
gc->txn = txn;
gc->dbi_state = txn->dbi_state;
gc->top_and_flags = z_fresh_mark;
env->prefault_write_activated = env->options.prefault_write;
if (env->prefault_write_activated) {
/* Проверка посредством minicore() существенно снижает затраты, но в
* простейших случаях (тривиальный бенчмарк) интегральная производительность
* становится вдвое меньше. А на платформах без mincore() и с проблемной
* подсистемой виртуальной памяти ситуация может быть многократно хуже.
* Поэтому избегаем затрат в ситуациях когда prefault-write скорее всего не
* нужна. */
const bool readahead_enabled = env->lck->readahead_anchor & 1;
const pgno_t readahead_edge = env->lck->readahead_anchor >> 1;
if (/* Не суетимся если GC почти пустая и БД маленькая */
(txn->dbs[FREE_DBI].branch_pages == 0 && txn->geo.now < 1234) ||
/* Не суетимся если страница в зоне включенного упреждающего чтения */
(readahead_enabled && pgno + num < readahead_edge))
env->prefault_write_activated = false;
}
retry_gc_refresh_oldest:;
txnid_t oldest = txn_snapshot_oldest(txn);
retry_gc_have_oldest:
if (unlikely(oldest >= txn->txnid)) {
ERROR("unexpected/invalid oldest-readed txnid %" PRIaTXN
" for current-txnid %" PRIaTXN,
oldest, txn->txnid);
ret.err = MDBX_PROBLEM;
goto fail;
}
const txnid_t detent = oldest + 1;
txnid_t id = 0;
MDBX_cursor_op op = MDBX_FIRST;
if (flags & ALLOC_LIFO) {
if (!txn->tw.gc.reclaimed) {
txn->tw.gc.reclaimed = txl_alloc();
if (unlikely(!txn->tw.gc.reclaimed)) {
ret.err = MDBX_ENOMEM;
goto fail;
}
}
/* Begin lookup backward from oldest reader */
id = detent - 1;
op = MDBX_SET_RANGE;
} else if (txn->tw.gc.last_reclaimed) {
/* Continue lookup forward from last-reclaimed */
id = txn->tw.gc.last_reclaimed + 1;
if (id >= detent)
goto depleted_gc;
op = MDBX_SET_RANGE;
}
next_gc:;
MDBX_val key;
key.iov_base = &id;
key.iov_len = sizeof(id);
#if MDBX_ENABLE_PROFGC
prof->rsteps += 1;
#endif /* MDBX_ENABLE_PROFGC */
/* Seek first/next GC record */
ret.err = cursor_ops(gc, &key, nullptr, op);
if (unlikely(ret.err != MDBX_SUCCESS)) {
if (unlikely(ret.err != MDBX_NOTFOUND))
goto fail;
if ((flags & ALLOC_LIFO) && op == MDBX_SET_RANGE) {
op = MDBX_PREV;
goto next_gc;
}
goto depleted_gc;
}
if (unlikely(key.iov_len != sizeof(txnid_t))) {
ERROR("%s/%d: %s", "MDBX_CORRUPTED", MDBX_CORRUPTED,
"invalid GC key-length");
ret.err = MDBX_CORRUPTED;
goto fail;
}
id = unaligned_peek_u64(4, key.iov_base);
if (flags & ALLOC_LIFO) {
op = MDBX_PREV;
if (id >= detent || is_already_reclaimed(txn, id))
goto next_gc;
} else {
op = MDBX_NEXT;
if (unlikely(id >= detent))
goto depleted_gc;
}
txn->flags &= ~txn_gc_drained;
/* Reading next GC record */
MDBX_val data;
page_t *const mp = gc->pg[gc->top];
if (unlikely((ret.err = node_read(gc, page_node(mp, gc->ki[gc->top]), &data,
mp)) != MDBX_SUCCESS))
goto fail;
pgno_t *gc_pnl = (pgno_t *)data.iov_base;
if (unlikely(data.iov_len % sizeof(pgno_t) ||
data.iov_len < MDBX_PNL_SIZEOF(gc_pnl) ||
!pnl_check(gc_pnl, txn->geo.first_unallocated))) {
ERROR("%s/%d: %s", "MDBX_CORRUPTED", MDBX_CORRUPTED,
"invalid GC value-length");
ret.err = MDBX_CORRUPTED;
goto fail;
}
const size_t gc_len = MDBX_PNL_GETSIZE(gc_pnl);
TRACE("gc-read: id #%" PRIaTXN " len %zu, re-list will %zu ", id, gc_len,
gc_len + MDBX_PNL_GETSIZE(txn->tw.relist));
if (unlikely(gc_len + MDBX_PNL_GETSIZE(txn->tw.relist) >=
env->maxgc_large1page)) {
/* Don't try to coalesce too much. */
if (flags & ALLOC_SHOULD_SCAN) {
eASSERT(env, flags & ALLOC_COALESCE);
eASSERT(env, !(flags & ALLOC_RESERVE));
eASSERT(env, num > 0);
#if MDBX_ENABLE_PROFGC
env->lck->pgops.gc_prof.coalescences += 1;
#endif /* MDBX_ENABLE_PROFGC */
TRACE("clear %s %s", "ALLOC_COALESCE", "since got threshold");
if (MDBX_PNL_GETSIZE(txn->tw.relist) >= num) {
eASSERT(env,
MDBX_PNL_LAST(txn->tw.relist) < txn->geo.first_unallocated &&
MDBX_PNL_FIRST(txn->tw.relist) <
txn->geo.first_unallocated);
if (likely(num == 1)) {
pgno = relist_get_single(txn);
goto done;
}
pgno = relist_get_sequence(txn, num, flags);
if (likely(pgno))
goto done;
}
flags -= ALLOC_COALESCE | ALLOC_SHOULD_SCAN;
}
if (unlikely(/* list is too long already */ MDBX_PNL_GETSIZE(
txn->tw.relist) >= env->options.rp_augment_limit) &&
((/* not a slot-request from gc-update */ num &&
/* have enough unallocated space */ txn->geo.upper >=
txn->geo.first_unallocated + num &&
monotime_since_cached(monotime_begin, &now_cache) +
txn->tw.gc.time_acc >=
env->options.gc_time_limit) ||
gc_len + MDBX_PNL_GETSIZE(txn->tw.relist) >= PAGELIST_LIMIT)) {
/* Stop reclaiming to avoid large/overflow the page list. This is a rare
* case while search for a continuously multi-page region in a
* large database, see https://libmdbx.dqdkfa.ru/dead-github/issues/123 */
NOTICE("stop reclaiming %s: %zu (current) + %zu "
"(chunk) -> %zu, rp_augment_limit %u",
likely(gc_len + MDBX_PNL_GETSIZE(txn->tw.relist) < PAGELIST_LIMIT)
? "since rp_augment_limit was reached"
: "to avoid PNL overflow",
MDBX_PNL_GETSIZE(txn->tw.relist), gc_len,
gc_len + MDBX_PNL_GETSIZE(txn->tw.relist),
env->options.rp_augment_limit);
goto depleted_gc;
}
}
/* Remember ID of readed GC record */
txn->tw.gc.last_reclaimed = id;
if (flags & ALLOC_LIFO) {
ret.err = txl_append(&txn->tw.gc.reclaimed, id);
if (unlikely(ret.err != MDBX_SUCCESS))
goto fail;
}
/* Append PNL from GC record to tw.relist */
ret.err = pnl_need(&txn->tw.relist, gc_len);
if (unlikely(ret.err != MDBX_SUCCESS))
goto fail;
if (LOG_ENABLED(MDBX_LOG_EXTRA)) {
DEBUG_EXTRA("readed GC-pnl txn %" PRIaTXN " root %" PRIaPGNO
" len %zu, PNL",
id, txn->dbs[FREE_DBI].root, gc_len);
for (size_t i = gc_len; i; i--)
DEBUG_EXTRA_PRINT(" %" PRIaPGNO, gc_pnl[i]);
DEBUG_EXTRA_PRINT(", first_unallocated %u\n", txn->geo.first_unallocated);
}
/* Merge in descending sorted order */
pnl_merge(txn->tw.relist, gc_pnl);
flags |= ALLOC_SHOULD_SCAN;
if (AUDIT_ENABLED()) {
if (unlikely(!pnl_check(txn->tw.relist, txn->geo.first_unallocated))) {
ERROR("%s/%d: %s", "MDBX_CORRUPTED", MDBX_CORRUPTED,
"invalid txn retired-list");
ret.err = MDBX_CORRUPTED;
goto fail;
}
} else {
eASSERT(env,
pnl_check_allocated(txn->tw.relist, txn->geo.first_unallocated));
}
eASSERT(env, dpl_check(txn));
eASSERT(env, MDBX_PNL_GETSIZE(txn->tw.relist) == 0 ||
MDBX_PNL_MOST(txn->tw.relist) < txn->geo.first_unallocated);
if (MDBX_ENABLE_REFUND && MDBX_PNL_GETSIZE(txn->tw.relist) &&
unlikely(MDBX_PNL_MOST(txn->tw.relist) ==
txn->geo.first_unallocated - 1)) {
/* Refund suitable pages into "unallocated" space */
txn_refund(txn);
}
eASSERT(env, pnl_check_allocated(txn->tw.relist, txn->geo.first_unallocated -
MDBX_ENABLE_REFUND));
/* Done for a kick-reclaim mode, actually no page needed */
if (unlikely(num == 0)) {
eASSERT(env, ret.err == MDBX_SUCCESS);
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "early-exit for slot", id,
MDBX_PNL_GETSIZE(txn->tw.relist));
goto early_exit;
}
/* TODO: delete reclaimed records */
eASSERT(env, op == MDBX_PREV || op == MDBX_NEXT);
if (flags & ALLOC_COALESCE) {
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "coalesce-continue", id,
MDBX_PNL_GETSIZE(txn->tw.relist));
goto next_gc;
}
scan:
eASSERT(env, flags & ALLOC_SHOULD_SCAN);
eASSERT(env, num > 0);
if (MDBX_PNL_GETSIZE(txn->tw.relist) >= num) {
eASSERT(env,
MDBX_PNL_LAST(txn->tw.relist) < txn->geo.first_unallocated &&
MDBX_PNL_FIRST(txn->tw.relist) < txn->geo.first_unallocated);
if (likely(num == 1)) {
eASSERT(env, !(flags & ALLOC_RESERVE));
pgno = relist_get_single(txn);
goto done;
}
pgno = relist_get_sequence(txn, num, flags);
if (likely(pgno))
goto done;
}
flags -= ALLOC_SHOULD_SCAN;
if (ret.err == MDBX_SUCCESS) {
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "continue-search", id,
MDBX_PNL_GETSIZE(txn->tw.relist));
goto next_gc;
}
depleted_gc:
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "gc-depleted", id,
MDBX_PNL_GETSIZE(txn->tw.relist));
ret.err = MDBX_NOTFOUND;
if (flags & ALLOC_SHOULD_SCAN)
goto scan;
txn->flags |= txn_gc_drained;
//-------------------------------------------------------------------------
/* There is no suitable pages in the GC and to be able to allocate
* we should CHOICE one of:
* - make a new steady checkpoint if reclaiming was stopped by
* the last steady-sync, or wipe it in the MDBX_UTTERLY_NOSYNC mode;
* - kick lagging reader(s) if reclaiming was stopped by ones of it.
* - extend the database file. */
/* Will use new pages from the map if nothing is suitable in the GC. */
newnext = txn->geo.first_unallocated + num;
/* Does reclaiming stopped at the last steady point? */
const meta_ptr_t recent = meta_recent(env, &txn->tw.troika);
const meta_ptr_t prefer_steady = meta_prefer_steady(env, &txn->tw.troika);
if (recent.ptr_c != prefer_steady.ptr_c && prefer_steady.is_steady &&
detent == prefer_steady.txnid + 1) {
DEBUG("gc-kick-steady: recent %" PRIaTXN "-%s, steady %" PRIaTXN
"-%s, detent %" PRIaTXN,
recent.txnid, durable_caption(recent.ptr_c), prefer_steady.txnid,
durable_caption(prefer_steady.ptr_c), detent);
const pgno_t autosync_threshold =
atomic_load32(&env->lck->autosync_threshold, mo_Relaxed);
const uint64_t autosync_period =
atomic_load64(&env->lck->autosync_period, mo_Relaxed);
uint64_t eoos_timestamp;
/* wipe the last steady-point if one of:
* - UTTERLY_NOSYNC mode AND auto-sync threshold is NOT specified
* - UTTERLY_NOSYNC mode AND free space at steady-point is exhausted
* otherwise, make a new steady-point if one of:
* - auto-sync threshold is specified and reached;
* - upper limit of database size is reached;
* - database is full (with the current file size)
* AND auto-sync threshold it NOT specified */
if (F_ISSET(env->flags, MDBX_UTTERLY_NOSYNC) &&
((autosync_threshold | autosync_period) == 0 ||
newnext >= prefer_steady.ptr_c->geometry.now)) {
/* wipe steady checkpoint in MDBX_UTTERLY_NOSYNC mode
* without any auto-sync threshold(s). */
#if MDBX_ENABLE_PROFGC
env->lck->pgops.gc_prof.wipes += 1;
#endif /* MDBX_ENABLE_PROFGC */
ret.err = meta_wipe_steady(env, detent);
DEBUG("gc-wipe-steady, rc %d", ret.err);
if (unlikely(ret.err != MDBX_SUCCESS))
goto fail;
eASSERT(env, prefer_steady.ptr_c !=
meta_prefer_steady(env, &txn->tw.troika).ptr_c);
goto retry_gc_refresh_oldest;
}
if ((autosync_threshold &&
atomic_load64(&env->lck->unsynced_pages, mo_Relaxed) >=
autosync_threshold) ||
(autosync_period &&
(eoos_timestamp =
atomic_load64(&env->lck->eoos_timestamp, mo_Relaxed)) &&
osal_monotime() - eoos_timestamp >= autosync_period) ||
newnext >= txn->geo.upper ||
((num == 0 || newnext >= txn->geo.end_pgno) &&
(autosync_threshold | autosync_period) == 0)) {
/* make steady checkpoint. */
#if MDBX_ENABLE_PROFGC
env->lck->pgops.gc_prof.flushes += 1;
#endif /* MDBX_ENABLE_PROFGC */
meta_t meta = *recent.ptr_c;
ret.err = dxb_sync_locked(env, env->flags & MDBX_WRITEMAP, &meta,
&txn->tw.troika);
DEBUG("gc-make-steady, rc %d", ret.err);
eASSERT(env, ret.err != MDBX_RESULT_TRUE);
if (unlikely(ret.err != MDBX_SUCCESS))
goto fail;
eASSERT(env, prefer_steady.ptr_c !=
meta_prefer_steady(env, &txn->tw.troika).ptr_c);
goto retry_gc_refresh_oldest;
}
}
if (unlikely(true ==
atomic_load32(&env->lck->rdt_refresh_flag, mo_AcquireRelease))) {
oldest = txn_snapshot_oldest(txn);
if (oldest >= detent)
goto retry_gc_have_oldest;
}
/* Avoid kick lagging reader(s) if is enough unallocated space
* at the end of database file. */
if (!(flags & ALLOC_RESERVE) && newnext <= txn->geo.end_pgno) {
eASSERT(env, pgno == 0);
goto done;
}
if (oldest < txn->txnid - xMDBX_TXNID_STEP) {
oldest = mvcc_kick_laggards(env, oldest);
if (oldest >= detent)
goto retry_gc_have_oldest;
}
//---------------------------------------------------------------------------
no_gc:
eASSERT(env, pgno == 0);
#ifndef MDBX_ENABLE_BACKLOG_DEPLETED
#define MDBX_ENABLE_BACKLOG_DEPLETED 0
#endif /* MDBX_ENABLE_BACKLOG_DEPLETED*/
if (MDBX_ENABLE_BACKLOG_DEPLETED &&
unlikely(!(txn->flags & txn_gc_drained))) {
ret.err = MDBX_BACKLOG_DEPLETED;
goto fail;
}
if (flags & ALLOC_RESERVE) {
ret.err = MDBX_NOTFOUND;
goto fail;
}
/* Will use new pages from the map if nothing is suitable in the GC. */
newnext = txn->geo.first_unallocated + num;
if (newnext <= txn->geo.end_pgno)
goto done;
if (newnext > txn->geo.upper || !txn->geo.grow_pv) {
NOTICE("gc-alloc: next %zu > upper %" PRIaPGNO, newnext, txn->geo.upper);
ret.err = MDBX_MAP_FULL;
goto fail;
}
eASSERT(env, newnext > txn->geo.end_pgno);
const size_t grow_step = pv2pages(txn->geo.grow_pv);
size_t aligned = pgno_align2os_pgno(
env, (pgno_t)(newnext + grow_step - newnext % grow_step));
if (aligned > txn->geo.upper)
aligned = txn->geo.upper;
eASSERT(env, aligned >= newnext);
VERBOSE("try growth datafile to %zu pages (+%zu)", aligned,
aligned - txn->geo.end_pgno);
ret.err = dxb_resize(env, txn->geo.first_unallocated, (pgno_t)aligned,
txn->geo.upper, implicit_grow);
if (ret.err != MDBX_SUCCESS) {
ERROR("unable growth datafile to %zu pages (+%zu), errcode %d", aligned,
aligned - txn->geo.end_pgno, ret.err);
goto fail;
}
env->txn->geo.end_pgno = (pgno_t)aligned;
eASSERT(env, pgno == 0);
//---------------------------------------------------------------------------
done:
ret.err = MDBX_SUCCESS;
if (likely((flags & ALLOC_RESERVE) == 0)) {
if (pgno) {
eASSERT(env,
pgno + num <= txn->geo.first_unallocated && pgno >= NUM_METAS);
eASSERT(env,
pnl_check_allocated(txn->tw.relist, txn->geo.first_unallocated -
MDBX_ENABLE_REFUND));
} else {
pgno = txn->geo.first_unallocated;
txn->geo.first_unallocated += (pgno_t)num;
eASSERT(env, txn->geo.first_unallocated <= txn->geo.end_pgno);
eASSERT(env,
pgno >= NUM_METAS && pgno + num <= txn->geo.first_unallocated);
}
ret = page_alloc_finalize(env, txn, mc, pgno, num);
if (unlikely(ret.err != MDBX_SUCCESS)) {
fail:
eASSERT(env, ret.err != MDBX_SUCCESS);
eASSERT(env,
pnl_check_allocated(txn->tw.relist, txn->geo.first_unallocated -
MDBX_ENABLE_REFUND));
int level;
const char *what;
if (flags & ALLOC_RESERVE) {
level = (flags & ALLOC_UNIMPORTANT) ? MDBX_LOG_DEBUG : MDBX_LOG_NOTICE;
what = num ? "reserve-pages" : "fetch-slot";
} else {
txn->flags |= MDBX_TXN_ERROR;
level = MDBX_LOG_ERROR;
what = "pages";
}
if (LOG_ENABLED(level))
debug_log(level, __func__, __LINE__,
"unable alloc %zu %s, alloc-flags 0x%x, err %d, txn-flags "
"0x%x, re-list-len %zu, loose-count %zu, gc: height %u, "
"branch %zu, leaf %zu, large %zu, entries %zu\n",
num, what, flags, ret.err, txn->flags,
MDBX_PNL_GETSIZE(txn->tw.relist), txn->tw.loose_count,
txn->dbs[FREE_DBI].height,
(size_t)txn->dbs[FREE_DBI].branch_pages,
(size_t)txn->dbs[FREE_DBI].leaf_pages,
(size_t)txn->dbs[FREE_DBI].large_pages,
(size_t)txn->dbs[FREE_DBI].items);
ret.page = nullptr;
}
if (num > 1)
txn->tw.gc.time_acc += monotime_since_cached(monotime_begin, &now_cache);
} else {
early_exit:
DEBUG("return nullptr for %zu pages for ALLOC_%s, rc %d", num,
num ? "RESERVE" : "SLOT", ret.err);
ret.page = nullptr;
}
#if MDBX_ENABLE_PROFGC
prof->rtime_monotonic += osal_monotime() - monotime_begin;
#endif /* MDBX_ENABLE_PROFGC */
return ret;
}
__hot pgr_t gc_alloc_single(const MDBX_cursor *const mc) {
MDBX_txn *const txn = mc->txn;
tASSERT(txn, mc->txn->flags & MDBX_TXN_DIRTY);
tASSERT(txn,
F_ISSET(*cursor_dbi_state(mc), DBI_LINDO | DBI_VALID | DBI_DIRTY));
/* If there are any loose pages, just use them */
while (likely(txn->tw.loose_pages)) {
#if MDBX_ENABLE_REFUND
if (unlikely(txn->tw.loose_refund_wl > txn->geo.first_unallocated)) {
txn_refund(txn);
if (!txn->tw.loose_pages)
break;
}
#endif /* MDBX_ENABLE_REFUND */
page_t *lp = txn->tw.loose_pages;
MDBX_ASAN_UNPOISON_MEMORY_REGION(lp, txn->env->ps);
VALGRIND_MAKE_MEM_DEFINED(&page_next(lp), sizeof(page_t *));
txn->tw.loose_pages = page_next(lp);
txn->tw.loose_count--;
DEBUG_EXTRA("db %d use loose page %" PRIaPGNO, cursor_dbi_dbg(mc),
lp->pgno);
tASSERT(txn, lp->pgno < txn->geo.first_unallocated);
tASSERT(txn, lp->pgno >= NUM_METAS);
VALGRIND_MAKE_MEM_UNDEFINED(page_data(lp), page_space(txn->env));
lp->txnid = txn->front_txnid;
pgr_t ret = {lp, MDBX_SUCCESS};
return ret;
}
if (likely(MDBX_PNL_GETSIZE(txn->tw.relist) > 0))
return page_alloc_finalize(txn->env, txn, mc, relist_get_single(txn), 1);
return gc_alloc_ex(mc, 1, ALLOC_DEFAULT);
}