From 086149b82d639540854839813b83355f2245bf49 Mon Sep 17 00:00:00 2001 From: tezc Date: Sun, 8 May 2022 01:12:01 +0300 Subject: [PATCH 1/2] Add sc_map_shrink() --- map/CMakeLists.txt | 2 +- map/map_test.c | 114 +++++++++++++++++++++++++++++++++++++++++++++ map/sc_map.c | 88 +++++++++++++++++++++------------- map/sc_map.h | 12 ++++- 4 files changed, 180 insertions(+), 36 deletions(-) diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt index bbcdc4d..d80d6b6 100644 --- a/map/CMakeLists.txt +++ b/map/CMakeLists.txt @@ -39,7 +39,7 @@ if (SC_BUILD_TEST) add_executable(${PROJECT_NAME}_test map_test.c sc_map.c) - target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_MAP_MAX=140000ul) + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_MAP_MAX=262144ul) if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND SC_USE_WRAP) if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR diff --git a/map/map_test.c b/map/map_test.c index 5bc8b9b..c6d3345 100644 --- a/map/map_test.c +++ b/map/map_test.c @@ -157,6 +157,18 @@ void test_32() assert(sc_map_found(&map)); sc_map_term_32(&map); + + assert(sc_map_init_32(&map, 0, 0)); + for (int i = 0; i < 128; i++) { + sc_map_put_32(&map, i, i); + } + for (int i = 0; i < 123; i++) { + sc_map_del_32(&map, i); + } + sc_map_shrink_32(&map); + assert(map.cap == 8); + sc_map_term_32(&map); + } void test_64() @@ -233,6 +245,17 @@ void test_64() assert(sc_map_found(&map)); sc_map_term_64(&map); + + assert(sc_map_init_64(&map, 0, 0)); + for (int i = 0; i < 128; i++) { + sc_map_put_64(&map, i, i); + } + for (int i = 0; i < 123; i++) { + sc_map_del_64(&map, i); + } + sc_map_shrink_64(&map); + assert(map.cap == 8); + sc_map_term_64(&map); } void test_64v() @@ -309,6 +332,17 @@ void test_64v() assert(sc_map_found(&map)); sc_map_term_64v(&map); + + assert(sc_map_init_64v(&map, 0, 0)); + for (int i = 0; i < 128; i++) { + sc_map_put_64v(&map, i, ""); + } + for (int i = 0; i < 123; i++) { + sc_map_del_64v(&map, i); + } + sc_map_shrink_64v(&map); + assert(map.cap == 8); + sc_map_term_64v(&map); } void test_64s() @@ -384,6 +418,17 @@ void test_64s() assert(sc_map_found(&map)); sc_map_term_64s(&map); + + assert(sc_map_init_64s(&map, 0, 0)); + for (int i = 0; i < 128; i++) { + sc_map_put_64s(&map, i, ""); + } + for (int i = 0; i < 123; i++) { + sc_map_del_64s(&map, i); + } + sc_map_shrink_64s(&map); + assert(map.cap == 8); + sc_map_term_64s(&map); } void test_str() @@ -489,6 +534,17 @@ void test_str() assert(sc_map_found(&map)); sc_map_term_str(&map); + + assert(sc_map_init_str(&map, 0, 0)); + for (int i = 0; i < 15; i++) { + sc_map_put_str(&map, &arr[i], ""); + } + for (int i = 0; i < 9; i++) { + sc_map_del_str(&map, &arr[i]); + } + sc_map_shrink_str(&map); + assert(map.cap == 8); + sc_map_term_str(&map); } void test_sv() @@ -592,6 +648,16 @@ void test_sv() assert(sc_map_found(&map)); sc_map_term_sv(&map); + + assert(sc_map_init_sv(&map, 0, 0)); + for (int i = 0; i < 15; i++) { + sc_map_put_sv(&map, &arr[i], ""); + } + for (int i = 0; i < 9; i++) { + sc_map_del_sv(&map, &arr[i]); + } + sc_map_shrink_sv(&map); + assert(map.cap == 8); } void test_s64() @@ -700,6 +766,18 @@ void test_s64() assert(sc_map_found(&map)); sc_map_term_s64(&map); + + assert(sc_map_init_s64(&map, 0, 0)); + for (int i = 0; i < 15; i++) { + sc_map_put_s64(&map, &arr[i], i); + } + for (int i = 0; i < 9; i++) { + sc_map_del_s64(&map, &arr[i]); + } + sc_map_shrink_s64(&map); + assert(map.cap == 8); + + sc_map_term_s64(&map); } void test0() @@ -1526,6 +1604,41 @@ void fail_test_s64(void) } #endif +void shrink_test() +{ + struct sc_map_32 map; + + sc_map_init_32(&map, 0, 75); + + for (int i = 0; i < 32; i++) { + sc_map_put_32(&map, i, i); + } + + sc_map_shrink_32(&map); + assert(map.cap == 64); + + for (int i = 0; i < 32; i++) { + sc_map_del_32(&map, i); + } + + sc_map_shrink_32(&map); + assert(map.cap == 1); + + for (int i = 0; i < 32; i++) { + sc_map_put_32(&map, i, i); + sc_map_shrink_32(&map); + } + assert(map.cap == 64); + + for (int i = 0; i < 24; i++) { + sc_map_del_32(&map, i); + sc_map_shrink_32(&map); + } + assert(map.cap == 16); + + sc_map_term_32(&map); +} + int main() { example(); @@ -1551,6 +1664,7 @@ int main() test_str(); test_sv(); test_s64(); + shrink_test(); return 0; } diff --git a/map/sc_map.c b/map/sc_map.c index 4e3d23c..e4e74a1 100644 --- a/map/sc_map.c +++ b/map/sc_map.c @@ -37,6 +37,21 @@ #define SC_MAP_MAX UINT32_MAX #endif +static uint32_t sc_map_pow2(uint32_t v) +{ + if (v < 8) { + return 8; + } + + v--; + for (uint32_t i = 1; i < sizeof(v) * 8; i *= 2) { + v |= v >> i; + } + v++; + + return v; +} + #define sc_map_def_strkey(name, K, V, cmp, hash_fn) \ bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, \ uint32_t hash) \ @@ -90,25 +105,10 @@ .cap = 1, \ .mem = (struct sc_map_item_##name *) &empty_items_##name[1]}; \ \ - static void *sc_map_alloc_##name(uint32_t *cap, uint32_t factor) \ + static void *sc_map_alloc_##name(uint32_t cap) \ { \ - uint32_t v = *cap; \ struct sc_map_item_##name *t; \ - \ - if (*cap > SC_MAP_MAX / factor) { \ - return NULL; \ - } \ - \ - /* Find next power of two */ \ - v = v < 8 ? 8 : (v * factor); \ - v--; \ - for (uint32_t i = 1; i < sizeof(v) * 8; i *= 2) { \ - v |= v >> i; \ - } \ - v++; \ - \ - *cap = v; \ - t = sc_map_calloc(sizeof(*t), v + 1); \ + t = sc_map_calloc(sizeof(*t), cap + 1); \ return t ? &t[1] : NULL; \ } \ \ @@ -124,11 +124,12 @@ \ if (cap == 0) { \ *m = sc_map_empty_##name; \ - m->load_fac = f; \ + m->load_fac = ((double) f / 100); \ return true; \ } \ \ - t = sc_map_alloc_##name(&cap, 1); \ + cap = sc_map_pow2(cap); \ + t = sc_map_alloc_##name(cap); \ if (t == NULL) { \ return false; \ } \ @@ -137,8 +138,8 @@ m->size = 0; \ m->used = false; \ m->cap = cap; \ - m->load_fac = f; \ - m->remap = (uint32_t) (m->cap * ((double) m->load_fac / 100)); \ + m->load_fac = ((double) f / 100); \ + m->remap = (uint32_t) (m->cap * m->load_fac); \ \ return true; \ } \ @@ -168,17 +169,12 @@ } \ } \ \ - static bool sc_map_remap_##name(struct sc_map_##name *m) \ + static bool sc_map_remap_##name(struct sc_map_##name *m, uint32_t cap) \ { \ - uint32_t pos, cap, mod; \ + uint32_t pos, mod; \ struct sc_map_item_##name *new; \ \ - if (m->size < m->remap) { \ - return true; \ - } \ - \ - cap = m->cap; \ - new = sc_map_alloc_##name(&cap, 2); \ + new = sc_map_alloc_##name(cap); \ if (new == NULL) { \ return false; \ } \ @@ -207,7 +203,7 @@ \ m->mem = new; \ m->cap = cap; \ - m->remap = (uint32_t) (m->cap * ((double) m->load_fac / 100)); \ + m->remap = (uint32_t) (m->cap * m->load_fac); \ \ return true; \ } \ @@ -219,9 +215,13 @@ \ m->oom = false; \ \ - if (!sc_map_remap_##name(m)) { \ - m->oom = true; \ - return 0; \ + if (m->size == m->remap) { \ + if (m->cap == SC_MAP_MAX || \ + !sc_map_remap_##name(m, \ + sc_map_pow2(m->cap * 2))) { \ + m->oom = true; \ + return 0; \ + } \ } \ \ if (key == 0) { \ @@ -336,6 +336,28 @@ \ return ret; \ } \ + } \ + \ + void sc_map_shrink_##name(struct sc_map_##name *m) \ + { \ + uint32_t v; \ + \ + if (m->size == 0) { \ + uint32_t fac = m->load_fac; \ + sc_map_term_##name(m); \ + sc_map_init_##name(m, 0, fac); \ + return; \ + } \ + \ + m->oom = false; \ + \ + v = m->size + (uint32_t) (m->size * (1 - m->load_fac)); \ + v = sc_map_pow2(v); \ + if (v == m->cap) { \ + return; \ + } \ + \ + m->oom = !sc_map_remap_##name(m, v); \ } static uint32_t sc_map_hash_32(uint32_t a) diff --git a/map/sc_map.h b/map/sc_map.h index 4949347..8bcc525 100644 --- a/map/sc_map.h +++ b/map/sc_map.h @@ -69,11 +69,11 @@ struct sc_map_item_##name *mem; \ uint32_t cap; \ uint32_t size; \ - uint32_t load_fac; \ uint32_t remap; \ bool used; \ bool oom; \ bool found; \ + double load_fac; \ }; \ \ /** \ @@ -111,6 +111,13 @@ */ \ void sc_map_clear_##name(struct sc_map_##name *map); \ \ + /** \ + * Shrink underlying array in the hashmap if possible. \ + * \ + * @param map map \ + */ \ + void sc_map_shrink_##name(struct sc_map_##name *map); \ + \ /** \ * Put element to the map \ * \ @@ -129,7 +136,7 @@ * Get element \ * \ * @param map map \ - * @param K key \ \ + * @param K key \ * @return current value if exists. \ * call sc_map_found() to see if returned value if valid. \ */ \ @@ -158,6 +165,7 @@ /** * @param map map * @return true if put operation failed with out of memory + * if shrink operation failed with out of memory */ #define sc_map_oom(map) ((map)->oom) From 8d325f947091acc582c48d5141d608a4cfae176a Mon Sep 17 00:00:00 2001 From: tezc Date: Sun, 8 May 2022 01:18:02 +0300 Subject: [PATCH 2/2] fix leak --- map/map_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/map/map_test.c b/map/map_test.c index c6d3345..cd9a49b 100644 --- a/map/map_test.c +++ b/map/map_test.c @@ -658,6 +658,7 @@ void test_sv() } sc_map_shrink_sv(&map); assert(map.cap == 8); + sc_map_term_sv(&map); } void test_s64()