diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d88fd8..6cf03f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ if(BUILD_TESTS) tests/random_op.c tests/realloc_move.c tests/reg.c + tests/shrink.c ) add_executable(buffer_set_tests ${TEST_SRCS}) diff --git a/include/buffer_set/buffer_set.h b/include/buffer_set/buffer_set.h index 51181c0..4f75f20 100644 --- a/include/buffer_set/buffer_set.h +++ b/include/buffer_set/buffer_set.h @@ -143,6 +143,14 @@ int buffer_set_verify( FILE * file ); +/** + * Shrink the buffer capacity of the set if it is underutilized. + * + * If the number of elements in the set is less than one quarter of the + * current buffer capacity, the capacity is reduced by half. + */ +void buffer_set_shrink(buffer_set_t * buffer_set); + void buffer_set_clear(buffer_set_t * buffer_set); void buffer_set_destroy(buffer_set_t * buffer_set); diff --git a/src/buffer_set.c b/src/buffer_set.c index 3c1b653..9415c0a 100644 --- a/src/buffer_set.c +++ b/src/buffer_set.c @@ -1019,6 +1019,93 @@ static uint16_t _buffer_set_clear( return idx; } +static uint16_t _buffer_set_move_tree( + buffer_set_t * buffer_set, + uint16_t src_idx, + void * src_buffer +) { + uint16_t idx = ++buffer_set->size; + size_t node_size = buffer_set->node_size; + struct node_s * dst_node = _get_node(buffer_set, idx); + struct node_s * src_node = (void*) (((char*)src_buffer) + (src_idx * node_size)); + + uint16_t left = src_node->left; + if (left != NULL_IDX) + { + left = _buffer_set_move_tree(buffer_set, left, src_buffer); + struct node_s * left_node = _get_node(buffer_set, left); + left_node->parent = idx; + } + dst_node->left = left; + + uint16_t right = src_node->right; + if (right != NULL_IDX) + { + right = _buffer_set_move_tree(buffer_set, right, src_buffer); + struct node_s * right_node = _get_node(buffer_set, right); + right_node->parent = idx; + } + dst_node->right = right; + + dst_node->balance = src_node->balance; + + void (*move)(void*, void*, void*) = buffer_set->move; + if (move == NULL) + { + size_t value_size = (node_size - _round(sizeof(struct node_s))); + memcpy( + _node_get_value(dst_node), + _node_get_value(src_node), + value_size + ); + } + else + move(dst_node, src_node, buffer_set->thunk); + + return idx; +} + +void buffer_set_shrink(buffer_set_t * buffer_set) +{ + uint16_t new_capacity = buffer_set->capacity; + while ((buffer_set->size + 1) < (new_capacity / 4)) + new_capacity /= 2; + + if (new_capacity < MIN_CAPACITY) + { + new_capacity = MIN_CAPACITY; + if (new_capacity >= buffer_set->capacity) + return; + } + + void * buffer = malloc(buffer_set->node_size * new_capacity); + if (buffer == NULL) + return; + + void * old_buffer = buffer_set->buffer; + buffer_set->buffer = buffer; + buffer_set->capacity = new_capacity; + + uint16_t root = buffer_set->root; + if (root != NULL_IDX) + { + buffer_set->size = 0; + root = _buffer_set_move_tree(buffer_set, root, old_buffer); + buffer_set->root = root; + struct node_s * node = _get_node(buffer_set, root); + node->parent = NULL_IDX; + } + + free(old_buffer); + + buffer_set->free_list = _make_free_list( + buffer, + buffer_set->node_size, + buffer_set->size + 1, + (new_capacity - buffer_set->size - 1) + ); +} + void buffer_set_clear(buffer_set_t * buffer_set) { const uint16_t root = buffer_set->root; diff --git a/tests/main.c b/tests/main.c index ddb5af3..fa7c816 100644 --- a/tests/main.c +++ b/tests/main.c @@ -43,6 +43,7 @@ int print_debug(); int random_op(); int realloc_move(); int reg(); +int shrink(); void run_test(int * failed_tests, const char * name, int (*test_func)()) { @@ -72,6 +73,7 @@ int main(int argc, const char * argv[]) RUN_TEST(print_debug); RUN_TEST(random_op); RUN_TEST(reg); + RUN_TEST(shrink); #undef RUN_TEST diff --git a/tests/shrink.c b/tests/shrink.c new file mode 100644 index 0000000..099651f --- /dev/null +++ b/tests/shrink.c @@ -0,0 +1,74 @@ +/* + * This file is part of BUFFER_SET library. + * Copyright (C) 2020 Sergey Zubarev, info@js-labs.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + */ + +#include +#include +#include +#include "test.h" + +int shrink() +{ + buffer_set_t* buffer_set = buffer_set_create(sizeof(int), 0, &int_cmp, NULL, NULL); + if (buffer_set == NULL) + { + printf("buffer_set_create() failed"); + return -1; + } + + for (int idx=0; idx<63; idx++) + { + int inserted = 0; + void * ptr = buffer_set_insert(buffer_set, &idx, &inserted); + *((int*)ptr) = idx; + } + + for (int idx=0; idx<50; idx++) + buffer_set_erase(buffer_set, &idx); + + buffer_set_shrink(buffer_set); + + int rc = 0; + buffer_set_iterator_t * it = buffer_set_begin(buffer_set); + + for (int idx=50; idx<63; idx++) + { + if (it == buffer_set_end(buffer_set)) + { + fprintf(stderr, "unexpected iterator end\n"); + rc = -1; + break; + } + + int * value = buffer_set_get_at(buffer_set, it); + if (*value != idx) + { + fprintf(stderr, "got %d instead of expected %d\n", *value, idx); + rc = -1; + break; + } + + it = buffer_set_iterator_next(buffer_set, it); + } + + if (it != buffer_set_end(buffer_set)) + { + fprintf(stderr, "iterator unexpectedly not end\n"); + rc = -1; + } + + buffer_set_destroy(buffer_set); + + return rc; +}