diff --git a/changelogs/unreleased/add-granularity-option.md b/changelogs/unreleased/add-granularity-option.md new file mode 100644 index 0000000000000000000000000000000000000000..bf6ba8a6d16c8b6b7d4d98950dfe71c585aefa27 --- /dev/null +++ b/changelogs/unreleased/add-granularity-option.md @@ -0,0 +1,5 @@ +## feature/core + +* Added granularity option to box.cfg{}. Granularity is an option + that allows user to set multiplicity of memory allocation in small + allocator. Granulatiry must be exponent of two and >= 4 (gh-5518). diff --git a/src/box/box.cc b/src/box/box.cc index 26cbe8aab92be92bcc3383df6921c3ec0d132a24..b4a1a5e07c0c3b820c15b811e993c33c2dac5557 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -849,6 +849,30 @@ box_check_sql_cache_size(int size) return 0; } +static void +box_check_small_alloc_options(void) +{ + /* + * If we use the int type, we may get an incorrect + * result if the user enters a large value. + */ + int64_t granularity = cfg_geti64("granularity"); + /* + * Granularity must be exponent of two and >= 4. + * We can use granularity value == 4 only because we used small + * memory allocator only for struct tuple, which doesn't require + * aligment. Also added an upper bound for granularity, since if + * the user enters too large value, he will get incomprehensible + * errors later. + */ + if (granularity < 4 || granularity > 1024 * 16 || + ! is_exp_of_two(granularity)) + tnt_raise(ClientError, ER_CFG, "granularity", + "must be greater than or equal to 4," + " less than or equal" + " to 1024 * 16 and exponent of two"); +} + void box_check_config(void) { @@ -878,6 +902,7 @@ box_check_config(void) if (box_check_memory_quota("memtx_memory") < 0) diag_raise(); box_check_memtx_min_tuple_size(cfg_geti64("memtx_min_tuple_size")); + box_check_small_alloc_options(); box_check_vinyl_options(); if (box_check_sql_cache_size(cfg_geti("sql_cache_size")) != 0) diag_raise(); @@ -2541,6 +2566,7 @@ engine_init() cfg_getd("memtx_memory"), cfg_geti("memtx_min_tuple_size"), cfg_geti("strip_core"), + cfg_geti("granularity"), cfg_getd("slab_alloc_factor")); engine_register((struct engine *)memtx); box_set_memtx_max_tuple_size(); diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index 574c8bef4a850278da3428c77e1264e59a7985d5..aac216932844c6b745916666b27eaa64857cf830 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -42,6 +42,7 @@ local default_cfg = { strip_core = true, memtx_min_tuple_size = 16, memtx_max_tuple_size = 1024 * 1024, + granularity = 8, slab_alloc_factor = 1.05, work_dir = nil, memtx_dir = ".", @@ -123,6 +124,7 @@ local template_cfg = { strip_core = 'boolean', memtx_min_tuple_size = 'number', memtx_max_tuple_size = 'number', + granularity = 'number', slab_alloc_factor = 'number', work_dir = 'string', memtx_dir = 'string', diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c index a4cd671f59798219c31dc5110bb630d885f3ad50..7d4c3789c5908a14092e7028dd68cd9817a282ce 100644 --- a/src/box/memtx_engine.c +++ b/src/box/memtx_engine.c @@ -1064,7 +1064,7 @@ memtx_engine_gc_f(va_list va) struct memtx_engine * memtx_engine_new(const char *snap_dirname, bool force_recovery, uint64_t tuple_arena_max_size, uint32_t objsize_min, - bool dontdump, float alloc_factor) + bool dontdump, unsigned granularity, float alloc_factor) { struct memtx_engine *memtx = calloc(1, sizeof(*memtx)); if (memtx == NULL) { @@ -1133,7 +1133,8 @@ memtx_engine_new(const char *snap_dirname, bool force_recovery, slab_cache_create(&memtx->slab_cache, &memtx->arena); float actual_alloc_factor; small_alloc_create(&memtx->alloc, &memtx->slab_cache, - objsize_min, alloc_factor, &actual_alloc_factor); + objsize_min, granularity, alloc_factor, + &actual_alloc_factor); say_info("Actual slab_alloc_factor calculated on the basis of desired " "slab_alloc_factor = %f", actual_alloc_factor); diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h index 8b380bf3ccc5f7335d7d94e74403899ac005e8cf..8bf35b5a4b668d17f32cde2cf4231970d102e7e1 100644 --- a/src/box/memtx_engine.h +++ b/src/box/memtx_engine.h @@ -213,7 +213,7 @@ struct memtx_engine * memtx_engine_new(const char *snap_dirname, bool force_recovery, uint64_t tuple_arena_max_size, uint32_t objsize_min, bool dontdump, - float alloc_factor); + unsigned granularity, float alloc_factor); int memtx_engine_recover_snapshot(struct memtx_engine *memtx, @@ -299,13 +299,13 @@ static inline struct memtx_engine * memtx_engine_new_xc(const char *snap_dirname, bool force_recovery, uint64_t tuple_arena_max_size, uint32_t objsize_min, bool dontdump, - float alloc_factor) + unsigned granularity, float alloc_factor) { struct memtx_engine *memtx; memtx = memtx_engine_new(snap_dirname, force_recovery, tuple_arena_max_size, objsize_min, dontdump, - alloc_factor); + granularity, alloc_factor); if (memtx == NULL) diag_raise(); return memtx; diff --git a/src/box/tuple.c b/src/box/tuple.c index c2023e4e8f41ca386d26001150f7fef0584649d5..47330481440039c8f6fe0afb0efc11794ade6a2a 100644 --- a/src/box/tuple.c +++ b/src/box/tuple.c @@ -315,7 +315,8 @@ tuple_init(field_name_hash_f hash) float actual_alloc_factor; small_alloc_create(&runtime_alloc, &cord()->slabc, OBJSIZE_MIN, - ALLOC_FACTOR, &actual_alloc_factor); + sizeof(intptr_t), ALLOC_FACTOR, + &actual_alloc_factor); mempool_create(&tuple_iterator_pool, &cord()->slabc, sizeof(struct tuple_iterator)); diff --git a/src/lib/small b/src/lib/small index 3ae6cbb4b51bb327aea30acaa83098bb25b03156..8ef2ec4b383e3b518fbfc462eee8d9c9786c3a64 160000 --- a/src/lib/small +++ b/src/lib/small @@ -1 +1 @@ -Subproject commit 3ae6cbb4b51bb327aea30acaa83098bb25b03156 +Subproject commit 8ef2ec4b383e3b518fbfc462eee8d9c9786c3a64 diff --git a/src/trivia/util.h b/src/trivia/util.h index 15f9881db873c32e094c9c740537c9fd8a999cc1..899e1eb7dbe1ce9ecbbdf9b6da4be9c188d4ecc9 100644 --- a/src/trivia/util.h +++ b/src/trivia/util.h @@ -39,6 +39,7 @@ #include <unistd.h> #include <inttypes.h> #include <assert.h> +#include <stdbool.h> #if defined(__cplusplus) extern "C" { @@ -563,6 +564,12 @@ cmp_i64(const void *_a, const void *_b) return COMPARE_RESULT(*a, *b); } +static inline bool +is_exp_of_two(unsigned n) +{ + return (n & (n - 1)) == 0; +} + /** * Put the current thread in sleep for the given number of * seconds. diff --git a/test/app-tap/init_script.result b/test/app-tap/init_script.result index 16c5b01d240ad98c01b7da077171ac6115abb73f..76fe2ea272cf3a9aca7a7f645a7be0183cde9146 100644 --- a/test/app-tap/init_script.result +++ b/test/app-tap/init_script.result @@ -15,6 +15,7 @@ feedback_enabled:true feedback_host:https://feedback.tarantool.io feedback_interval:3600 force_recovery:false +granularity:8 hot_standby:false listen:port log:tarantool.log diff --git a/test/box/admin.result b/test/box/admin.result index 05debe67317f65854ddf0670ccadbeedb0c98f8a..c9a6ff9e48616df56bff9af6af21c931da3b4f49 100644 --- a/test/box/admin.result +++ b/test/box/admin.result @@ -51,6 +51,8 @@ cfg_filter(box.cfg) - 3600 - - force_recovery - false + - - granularity + - 8 - - hot_standby - false - - listen diff --git a/test/box/cfg.result b/test/box/cfg.result index 22a720c2cdd36c4f2237cce6194c135747d7ffc1..ae37b28f07c08b2df0fd1a8fc3a9809ada0e8a08 100644 --- a/test/box/cfg.result +++ b/test/box/cfg.result @@ -39,6 +39,8 @@ cfg_filter(box.cfg) | - 3600 | - - force_recovery | - false + | - - granularity + | - 8 | - - hot_standby | - false | - - listen @@ -154,6 +156,8 @@ cfg_filter(box.cfg) | - 3600 | - - force_recovery | - false + | - - granularity + | - 8 | - - hot_standby | - false | - - listen diff --git a/test/box/gh-5518-add-granularity-option.lua b/test/box/gh-5518-add-granularity-option.lua new file mode 100644 index 0000000000000000000000000000000000000000..d040b677bd9132a4896bcb31fde221ea0c3a8069 --- /dev/null +++ b/test/box/gh-5518-add-granularity-option.lua @@ -0,0 +1,20 @@ +#!/usr/bin/env tarantool + +local granularity = 8 +if arg[1] then + granularity = tonumber(arg[1]) +end + +require('console').listen(os.getenv('ADMIN')) + +box.cfg({ + listen = os.getenv("LISTEN"), + granularity = granularity, +}) + +if box.space.test ~= nil then + box.space.test:drop() +end +local s = box.schema.space.create('test') +local _ = s:create_index('test') +for i = 1, 1000 do s:insert{i, i + 1} end diff --git a/test/box/gh-5518-add-granularity-option.result b/test/box/gh-5518-add-granularity-option.result new file mode 100644 index 0000000000000000000000000000000000000000..eeb2fad5da065818daf2660e8e5132c721c28d2b --- /dev/null +++ b/test/box/gh-5518-add-granularity-option.result @@ -0,0 +1,144 @@ +-- test-run result file version 2 +env = require('test_run') + | --- + | ... +test_run = env.new() + | --- + | ... + +test_run:cmd("setopt delimiter ';'") + | --- + | - true + | ... +function check_slab_stats(slab_stats, granularity) + for _, stats in pairs(slab_stats) do + assert(type(stats) == "table") + for key, value in pairs(stats) do + if key == "item_size" then + assert((value % granularity) == 0) + end + end + end +end; + | --- + | ... +function get_slab_info_and_check_stats(granularity) + local slab_stats = test_run:eval('test', "box.slab.stats()") + local slab_info = test_run:eval('test', "box.slab.info()") + check_slab_stats(slab_stats[1], granularity) + return slab_info[1] +end; + | --- + | ... +test_run:cmd('create server test with script=\z + "box/gh-5518-add-granularity-option.lua"'); + | --- + | - true + | ... +test_run:cmd("setopt delimiter ''"); + | --- + | - true + | ... +-- Start server test with granularity == 2 failed +-- (must be greater than or equal to 4) +test_run:cmd('start server test with args="2" with crash_expected=True') + | --- + | - false + | ... +-- Start server test with granularity == 7 failed +-- (must be exponent of 2) +test_run:cmd('start server test with args="7" with crash_expected=True') + | --- + | - false + | ... + +test_run:cmd('start server test with args="4"') + | --- + | - true + | ... +-- Granularity determines not only alignment of objects, +-- but also size of the objects in the pool. Thus, the greater +-- the granularity, the greater the memory loss per one memory allocation, +-- but tuples with different sizes are allocated from the same mempool, +-- and we do not lose memory on the slabs, when we have highly +-- distributed tuple sizes. This is somewhat similar to a large alloc factor + +-- Try to insert/delete to space, in case when UB sanitizer on, +-- we check correct memory aligment +slab_info_4 = get_slab_info_and_check_stats(4) + | --- + | ... +test_run:cmd('stop server test') + | --- + | - true + | ... + +test_run:cmd('start server test with args="64"') + | --- + | - true + | ... +slab_info_64 = get_slab_info_and_check_stats(64) + | --- + | ... +test_run:cmd('stop server test') + | --- + | - true + | ... + +-- Start server test with granularity = 8192 +-- This is not a user case (such big granularity leads +-- to an unacceptably large memory consumption). +-- For test purposes only. +test_run:cmd('start server test with args="8192"') + | --- + | - true + | ... +slab_info_8192 = get_slab_info_and_check_stats(8192) + | --- + | ... +test_run:cmd('stop server test') + | --- + | - true + | ... + +-- Check that the larger the granularity, +-- the larger memory usage. +test_run:cmd("setopt delimiter ';'") + | --- + | - true + | ... +function percent_string_to_number(str) + local p = string.find(str, "%%") + return string.sub(str, 0, p - 1) +end; + | --- + | ... +for key, value_4 in pairs(slab_info_4) do + local value_64 = slab_info_64[key] + local value_8192 = slab_info_8192[key] + if (key == "items_used_ratio" or key == "arena_used_ratio") then + value_4 = percent_string_to_number(value_4) + value_64 = percent_string_to_number(value_64) + value_8192 = percent_string_to_number(value_8192) + end + if (key == "items_used" or key == "arena_used" or + key == "items_used_ratio" or key == "arena_used_ratio") then + assert(tonumber(value_4) < tonumber(value_64) and + tonumber(value_64) < tonumber(value_8192)) + end +end; + | --- + | ... +test_run:cmd("setopt delimiter ''"); + | --- + | - true + | ... + +test_run:cmd('cleanup server test') + | --- + | - true + | ... +test_run:cmd('delete server test') + | --- + | - true + | ... diff --git a/test/box/gh-5518-add-granularity-option.test.lua b/test/box/gh-5518-add-granularity-option.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..2943d50e4aad2433ef200c42b14f68c7071a47fa --- /dev/null +++ b/test/box/gh-5518-add-granularity-option.test.lua @@ -0,0 +1,80 @@ +env = require('test_run') +test_run = env.new() + +test_run:cmd("setopt delimiter ';'") +function check_slab_stats(slab_stats, granularity) + for _, stats in pairs(slab_stats) do + assert(type(stats) == "table") + for key, value in pairs(stats) do + if key == "item_size" then + assert((value % granularity) == 0) + end + end + end +end; +function get_slab_info_and_check_stats(granularity) + local slab_stats = test_run:eval('test', "box.slab.stats()") + local slab_info = test_run:eval('test', "box.slab.info()") + check_slab_stats(slab_stats[1], granularity) + return slab_info[1] +end; +test_run:cmd('create server test with script=\z + "box/gh-5518-add-granularity-option.lua"'); +test_run:cmd("setopt delimiter ''"); +-- Start server test with granularity == 2 failed +-- (must be greater than or equal to 4) +test_run:cmd('start server test with args="2" with crash_expected=True') +-- Start server test with granularity == 7 failed +-- (must be exponent of 2) +test_run:cmd('start server test with args="7" with crash_expected=True') + +test_run:cmd('start server test with args="4"') +-- Granularity determines not only alignment of objects, +-- but also size of the objects in the pool. Thus, the greater +-- the granularity, the greater the memory loss per one memory allocation, +-- but tuples with different sizes are allocated from the same mempool, +-- and we do not lose memory on the slabs, when we have highly +-- distributed tuple sizes. This is somewhat similar to a large alloc factor + +-- Try to insert/delete to space, in case when UB sanitizer on, +-- we check correct memory aligment +slab_info_4 = get_slab_info_and_check_stats(4) +test_run:cmd('stop server test') + +test_run:cmd('start server test with args="64"') +slab_info_64 = get_slab_info_and_check_stats(64) +test_run:cmd('stop server test') + +-- Start server test with granularity = 8192 +-- This is not a user case (such big granularity leads +-- to an unacceptably large memory consumption). +-- For test purposes only. +test_run:cmd('start server test with args="8192"') +slab_info_8192 = get_slab_info_and_check_stats(8192) +test_run:cmd('stop server test') + +-- Check that the larger the granularity, +-- the larger memory usage. +test_run:cmd("setopt delimiter ';'") +function percent_string_to_number(str) + local p = string.find(str, "%%") + return string.sub(str, 0, p - 1) +end; +for key, value_4 in pairs(slab_info_4) do + local value_64 = slab_info_64[key] + local value_8192 = slab_info_8192[key] + if (key == "items_used_ratio" or key == "arena_used_ratio") then + value_4 = percent_string_to_number(value_4) + value_64 = percent_string_to_number(value_64) + value_8192 = percent_string_to_number(value_8192) + end + if (key == "items_used" or key == "arena_used" or + key == "items_used_ratio" or key == "arena_used_ratio") then + assert(tonumber(value_4) < tonumber(value_64) and + tonumber(value_64) < tonumber(value_8192)) + end +end; +test_run:cmd("setopt delimiter ''"); + +test_run:cmd('cleanup server test') +test_run:cmd('delete server test')