diff --git a/mdbx.h b/mdbx.h index db395328..a7715fd4 100644 --- a/mdbx.h +++ b/mdbx.h @@ -2583,16 +2583,17 @@ struct MDBX_envinfo { * first process opened the database after everyone had previously closed it). */ struct { - uint64_t newly; /**< Quantity of a new pages added */ - uint64_t cow; /**< Quantity of pages copied for update */ - uint64_t clone; /**< Quantity of parent's dirty pages clones - for nested transactions */ - uint64_t split; /**< Page splits */ - uint64_t merge; /**< Page merges */ - uint64_t spill; /**< Quantity of spilled dirty pages */ - uint64_t unspill; /**< Quantity of unspilled/reloaded pages */ - uint64_t wops; /**< Number of explicit write operations (not a pages) - to a disk */ + uint64_t newly; /**< Quantity of a new pages added */ + uint64_t cow; /**< Quantity of pages copied for update */ + uint64_t clone; /**< Quantity of parent's dirty pages clones + for nested transactions */ + uint64_t split; /**< Page splits */ + uint64_t merge; /**< Page merges */ + uint64_t spill; /**< Quantity of spilled dirty pages */ + uint64_t unspill; /**< Quantity of unspilled/reloaded pages */ + uint64_t wops; /**< Number of explicit write operations (not a pages) + to a disk */ + uint64_t prefault; /**< Number of prefault write operations (not a pages) */ uint64_t msync; /**< Number of explicit msync-to-disk operations (not a pages) */ uint64_t diff --git a/src/core.c b/src/core.c index 48a64091..6babacae 100644 --- a/src/core.c +++ b/src/core.c @@ -6773,18 +6773,68 @@ static __inline pgr_t page_alloc_finalize(MDBX_env *const env, ret.page = pgno2page(env, pgno); MDBX_ASAN_UNPOISON_MEMORY_REGION(ret.page, pgno2bytes(env, num)); VALGRIND_MAKE_MEM_UNDEFINED(ret.page, pgno2bytes(env, num)); + +#if MDBX_ENABLE_PREFAULT + /* Содержимое выделенной страницы не нужно, но если страница отсутствует + * в ОЗУ (что весьма вероятно), то любое обращение к ней приведет + * к page-fault: + * - прерыванию по отсутствию страницы; + * - переключение контекста в режим ядра с засыпанием процесса; + * - чтение страницы с диска; + * - обновление PTE и пробуждением процесса; + * - переключение контекста по доступности ЦПУ. + * + * Пытаемся минимизировать накладные расходы записывая страницу, что при + * наличии unified page cache приведет к появлению страницы в ОЗУ без чтения + * с диска. При этом запись на диск должна быть отложена адекватным ядром, + * так как страница отображена в память в режиме чтения-записи и следом в + * неё пишет ЦПУ. */ + void *const pattern = ptr_disp( + env->me_pbuf, + (env->me_flags & MDBX_PAGEPERTURB) ? env->me_psize : env->me_psize * 2); + size_t file_offset = pgno2bytes(env, pgno); + /* TODO: добавить проверку через mincore() c кэшированием результатов. */ + if (likely(num == 1)) { + osal_pwrite(env->me_lazy_fd, pattern, env->me_psize, file_offset); + } else { + struct iovec iov[MDBX_AUXILARY_IOV_MAX]; + iov[0].iov_len = env->me_psize; + iov[0].iov_base = pattern; + size_t n = 1, left = num - 1; + do { + iov[n].iov_len = env->me_psize; + iov[n].iov_base = pattern; + if (++n == MDBX_AUXILARY_IOV_MAX) { + osal_pwritev(env->me_lazy_fd, iov, MDBX_AUXILARY_IOV_MAX, + file_offset); + file_offset += pgno2bytes(env, MDBX_AUXILARY_IOV_MAX); +#if MDBX_ENABLE_PGOP_STAT + env->me_lck->mti_pgop_stat.prefault.weak += 1; +#endif /* MDBX_ENABLE_PGOP_STAT */ + n = 0; + } + } while (--left); + osal_pwritev(env->me_lazy_fd, iov, n, file_offset); + } +#if MDBX_ENABLE_PGOP_STAT + env->me_lck->mti_pgop_stat.prefault.weak += 1; +#endif /* MDBX_ENABLE_PGOP_STAT */ +#else + if (unlikely(env->me_flags & MDBX_PAGEPERTURB)) + memset(ret.page, -1, pgno2bytes(env, num)); +#endif /* MDBX_ENABLE_PREFAULT */ + } else { ret.page = page_malloc(txn, num); if (unlikely(!ret.page)) { ret.err = MDBX_ENOMEM; goto bailout; } + if (unlikely(env->me_flags & MDBX_PAGEPERTURB)) + memset(ret.page, -1, pgno2bytes(env, num)); } - if (unlikely(env->me_flags & MDBX_PAGEPERTURB)) - memset(ret.page, -1, pgno2bytes(env, num)); VALGRIND_MAKE_MEM_UNDEFINED(ret.page, pgno2bytes(env, num)); - ret.page->mp_pgno = pgno; ret.page->mp_leaf2_ksize = 0; ret.page->mp_flags = 0; @@ -14428,6 +14478,7 @@ __cold int mdbx_env_openW(MDBX_env *env, const wchar_t *pathname, rc = alloc_page_buf(env); if (rc == MDBX_SUCCESS) { memset(env->me_pbuf, -1, env->me_psize * 2); + memset(ptr_disp(env->me_pbuf, env->me_psize * 2), 0, env->me_psize); MDBX_txn *txn = osal_calloc(1, size); if (txn) { txn->mt_dbs = ptr_disp(txn, tsize); @@ -21586,6 +21637,8 @@ __cold static int fetch_envinfo_ex(const MDBX_env *env, const MDBX_txn *txn, atomic_load64(&lck->mti_pgop_stat.unspill, mo_Relaxed); arg->mi_pgop_stat.wops = atomic_load64(&lck->mti_pgop_stat.wops, mo_Relaxed); + arg->mi_pgop_stat.prefault = + atomic_load64(&lck->mti_pgop_stat.prefault, mo_Relaxed); arg->mi_pgop_stat.msync = atomic_load64(&lck->mti_pgop_stat.msync, mo_Relaxed); arg->mi_pgop_stat.fsync = @@ -24706,6 +24759,7 @@ __dll_export " MDBX_AVOID_MSYNC=" MDBX_STRINGIFY(MDBX_AVOID_MSYNC) " MDBX_ENABLE_REFUND=" MDBX_STRINGIFY(MDBX_ENABLE_REFUND) " MDBX_ENABLE_MADVISE=" MDBX_STRINGIFY(MDBX_ENABLE_MADVISE) + " MDBX_ENABLE_PREFAULT=" MDBX_STRINGIFY(MDBX_ENABLE_PREFAULT) " MDBX_ENABLE_PGOP_STAT=" MDBX_STRINGIFY(MDBX_ENABLE_PGOP_STAT) " MDBX_ENABLE_PROFGC=" MDBX_STRINGIFY(MDBX_ENABLE_PROFGC) #if MDBX_DISABLE_VALIDATION diff --git a/src/internals.h b/src/internals.h index 3c206f1c..8f44ec18 100644 --- a/src/internals.h +++ b/src/internals.h @@ -619,6 +619,8 @@ typedef struct pgop_stat { MDBX_atomic_uint64_t fsync; /* Number of explicit fsync/flush-to-disk operations */ + MDBX_atomic_uint64_t prefault; /* Number of prefault write operations */ + /* Статистика для профилирования GC. * Логически эти данные может быть стоит вынести в другую структуру, * но разница будет сугубо косметическая. */ diff --git a/src/options.h b/src/options.h index 2ab0dce6..dda2b2ad 100644 --- a/src/options.h +++ b/src/options.h @@ -87,6 +87,18 @@ #error MDBX_ENABLE_PGOP_STAT must be defined as 0 or 1 #endif /* MDBX_ENABLE_PGOP_STAT */ +/** Controls prevention of page-faults of reclaimed and allocated pages in the + * MDBX_WRITEMAP mode by clearing ones through file handle before touching. */ +#ifndef MDBX_ENABLE_PREFAULT +#if MDBX_MMAP_INCOHERENT_FILE_WRITE +#define MDBX_ENABLE_PREFAULT 0 +#else +#define MDBX_ENABLE_PREFAULT 1 +#endif +#elif !(MDBX_ENABLE_PREFAULT == 0 || MDBX_ENABLE_PREFAULT == 1) +#error MDBX_ENABLE_PREFAULT must be defined as 0 or 1 +#endif /* MDBX_ENABLE_PREFAULT */ + /** Enables chunking long list of retired pages during huge transactions commit * to avoid use sequences of pages. */ #ifndef MDBX_ENABLE_BIGFOOT