mirror of
https://github.com/bkthomps/Containers.git
synced 2025-11-16 04:24:47 +00:00
Optimize deque block allocations (#122)
Rather than allocating a new block every time it is needed, it will check the other end of the outer array to see if blocks are available but not in use. This way, if a deque is being used like a queue, the block count will not arbitrarily grow.
This commit is contained in:
100
src/deque.c
100
src/deque.c
@@ -34,6 +34,8 @@ struct internal_deque {
|
||||
size_t start_index;
|
||||
size_t end_index;
|
||||
size_t block_count;
|
||||
size_t alloc_block_start;
|
||||
size_t alloc_block_end;
|
||||
char **data;
|
||||
};
|
||||
|
||||
@@ -70,7 +72,9 @@ deque deque_init(const size_t data_size)
|
||||
init->block_size * BKTHOMPS_DEQUE_INITIAL_BLOCK_COUNT / 2;
|
||||
init->end_index = init->start_index;
|
||||
init->block_count = BKTHOMPS_DEQUE_INITIAL_BLOCK_COUNT;
|
||||
init->data = calloc(init->block_count, sizeof(char *));
|
||||
init->alloc_block_start = init->start_index / init->block_size;
|
||||
init->alloc_block_end = init->alloc_block_start;
|
||||
init->data = malloc(init->block_count * sizeof(char *));
|
||||
if (!init->data) {
|
||||
free(init);
|
||||
return NULL;
|
||||
@@ -81,8 +85,7 @@ deque deque_init(const size_t data_size)
|
||||
free(init);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(init->data + init->start_index / init->block_size, &block,
|
||||
sizeof(char *));
|
||||
init->data[init->alloc_block_start] = block;
|
||||
return init;
|
||||
}
|
||||
|
||||
@@ -134,20 +137,18 @@ bk_err deque_trim(deque me)
|
||||
}
|
||||
memcpy(updated_data, me->data + start_block_index,
|
||||
updated_block_count * sizeof(char *));
|
||||
for (i = 0; i < start_block_index; i++) {
|
||||
char *block;
|
||||
memcpy(&block, me->data + i, sizeof(char *));
|
||||
free(block);
|
||||
for (i = me->alloc_block_start; i < start_block_index; i++) {
|
||||
free(me->data[i]);
|
||||
}
|
||||
for (i = end_block_index + 1; i < me->block_count; i++) {
|
||||
char *block;
|
||||
memcpy(&block, me->data + i, sizeof(char *));
|
||||
free(block);
|
||||
for (i = end_block_index + 1; i <= me->alloc_block_end; i++) {
|
||||
free(me->data[i]);
|
||||
}
|
||||
free(me->data);
|
||||
me->start_index -= start_block_index * me->block_size;
|
||||
me->end_index -= start_block_index * me->block_size;
|
||||
me->block_count = updated_block_count;
|
||||
me->alloc_block_start = 0;
|
||||
me->alloc_block_end = updated_block_count - 1;
|
||||
me->data = updated_data;
|
||||
return BK_OK;
|
||||
}
|
||||
@@ -235,18 +236,15 @@ bk_err deque_add_all(deque me, void *const arr, const size_t size)
|
||||
if (!temp) {
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
memset(temp + me->block_count, 0, appended_blocks * sizeof(char *));
|
||||
me->data = temp;
|
||||
me->block_count = new_block_count;
|
||||
}
|
||||
for (i = block_index + 1; i <= block_index + needed_blocks; i++) {
|
||||
if (me->data[i]) {
|
||||
continue;
|
||||
}
|
||||
for (i = me->alloc_block_end + 1; i <= block_index + needed_blocks; i++) {
|
||||
me->data[i] = malloc(me->block_size * me->data_size);
|
||||
if (!me->data[i]) {
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
me->alloc_block_end++;
|
||||
}
|
||||
offset = first_block_space * me->data_size;
|
||||
memcpy(me->data[block_index] + inner_index * me->data_size, arr, offset);
|
||||
@@ -307,23 +305,28 @@ bk_err deque_push_front(deque me, void *const data)
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
memmove(temp + added_blocks, temp, me->block_count * sizeof(char *));
|
||||
memset(temp, 0, added_blocks * sizeof(char *));
|
||||
me->data = temp;
|
||||
me->block_count = new_block_count;
|
||||
me->start_index += added_blocks * me->block_size;
|
||||
me->end_index += added_blocks * me->block_size;
|
||||
me->alloc_block_start += added_blocks;
|
||||
me->alloc_block_end += added_blocks;
|
||||
}
|
||||
if (me->start_index % me->block_size == 0) {
|
||||
char *block;
|
||||
const size_t previous_block_index =
|
||||
me->start_index / me->block_size - 1;
|
||||
memcpy(&block, me->data + previous_block_index, sizeof(char *));
|
||||
if (!block) {
|
||||
block = malloc(me->block_size * me->data_size);
|
||||
if (!block) {
|
||||
return -BK_ENOMEM;
|
||||
const size_t add_block_index = me->start_index / me->block_size - 1;
|
||||
if (add_block_index < me->alloc_block_start) {
|
||||
const size_t end_block = (me->end_index - 1) / me->block_size;
|
||||
if (end_block < me->alloc_block_end) {
|
||||
me->data[add_block_index] = me->data[me->alloc_block_end];
|
||||
me->alloc_block_end--;
|
||||
} else {
|
||||
me->data[add_block_index] =
|
||||
malloc(me->block_size * me->data_size);
|
||||
if (!me->data[add_block_index]) {
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
}
|
||||
memcpy(me->data + previous_block_index, &block, sizeof(char *));
|
||||
me->alloc_block_start--;
|
||||
}
|
||||
}
|
||||
me->start_index--;
|
||||
@@ -355,30 +358,32 @@ bk_err deque_push_back(deque me, void *const data)
|
||||
{
|
||||
if (me->end_index == me->block_count * me->block_size) {
|
||||
const size_t new_block_count = deque_get_new_block_count(me);
|
||||
size_t added_blocks;
|
||||
char **temp;
|
||||
if (new_block_count == 0) {
|
||||
return -BK_ERANGE;
|
||||
}
|
||||
added_blocks = new_block_count - me->block_count;
|
||||
temp = realloc(me->data, new_block_count * sizeof(char *));
|
||||
if (!temp) {
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
memset(temp + me->block_count, 0, added_blocks * sizeof(char *));
|
||||
me->data = temp;
|
||||
me->block_count = new_block_count;
|
||||
}
|
||||
if (me->end_index % me->block_size == 0) {
|
||||
char *block;
|
||||
const size_t tentative_block_index = me->end_index / me->block_size;
|
||||
memcpy(&block, me->data + tentative_block_index, sizeof(char *));
|
||||
if (!block) {
|
||||
block = malloc(me->block_size * me->data_size);
|
||||
if (!block) {
|
||||
return -BK_ENOMEM;
|
||||
const size_t add_block_index = me->end_index / me->block_size;
|
||||
if (add_block_index > me->alloc_block_end) {
|
||||
const size_t start_block = me->start_index / me->block_size;
|
||||
if (start_block > me->alloc_block_start) {
|
||||
me->data[add_block_index] = me->data[me->alloc_block_start];
|
||||
me->alloc_block_start++;
|
||||
} else {
|
||||
me->data[add_block_index] =
|
||||
malloc(me->block_size * me->data_size);
|
||||
if (!me->data[add_block_index]) {
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
}
|
||||
memcpy(me->data + tentative_block_index, &block, sizeof(char *));
|
||||
me->alloc_block_end++;
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -586,8 +591,8 @@ bk_err deque_clear(deque me)
|
||||
{
|
||||
size_t i;
|
||||
char *updated_block;
|
||||
char **updated_data = calloc(BKTHOMPS_DEQUE_INITIAL_BLOCK_COUNT,
|
||||
sizeof(char *));
|
||||
char **updated_data =
|
||||
malloc(BKTHOMPS_DEQUE_INITIAL_BLOCK_COUNT * sizeof(char *));
|
||||
if (!updated_data) {
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
@@ -596,18 +601,17 @@ bk_err deque_clear(deque me)
|
||||
free(updated_data);
|
||||
return -BK_ENOMEM;
|
||||
}
|
||||
for (i = 0; i < me->block_count; i++) {
|
||||
char *block;
|
||||
memcpy(&block, me->data + i, sizeof(char *));
|
||||
free(block);
|
||||
for (i = me->alloc_block_start; i <= me->alloc_block_end; i++) {
|
||||
free(me->data[i]);
|
||||
}
|
||||
free(me->data);
|
||||
me->start_index = me->block_size * BKTHOMPS_DEQUE_INITIAL_BLOCK_COUNT / 2;
|
||||
me->end_index = me->start_index;
|
||||
me->block_count = BKTHOMPS_DEQUE_INITIAL_BLOCK_COUNT;
|
||||
me->alloc_block_start = me->start_index / me->block_size;
|
||||
me->alloc_block_end = me->alloc_block_start;
|
||||
me->data = updated_data;
|
||||
memcpy(me->data + me->start_index / me->block_size, &updated_block,
|
||||
sizeof(char *));
|
||||
me->data[me->alloc_block_start] = updated_block;
|
||||
return BK_OK;
|
||||
}
|
||||
|
||||
@@ -622,10 +626,8 @@ bk_err deque_clear(deque me)
|
||||
deque deque_destroy(deque me)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < me->block_count; i++) {
|
||||
char *block;
|
||||
memcpy(&block, me->data + i, sizeof(char *));
|
||||
free(block);
|
||||
for (i = me->alloc_block_start; i <= me->alloc_block_end; i++) {
|
||||
free(me->data[i]);
|
||||
}
|
||||
free(me->data);
|
||||
free(me);
|
||||
|
||||
@@ -268,13 +268,14 @@ static void test_large_elements(void)
|
||||
#if STUB_MALLOC
|
||||
static void test_init_out_of_memory(void)
|
||||
{
|
||||
fail_calloc = 1;
|
||||
assert(!deque_init(sizeof(int)));
|
||||
fail_malloc = 1;
|
||||
assert(!deque_init(sizeof(int)));
|
||||
fail_malloc = 1;
|
||||
delay_fail_malloc = 1;
|
||||
assert(!deque_init(sizeof(int)));
|
||||
fail_malloc = 1;
|
||||
delay_fail_malloc = 2;
|
||||
assert(!deque_init(sizeof(int)));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -342,7 +343,7 @@ static void test_clear_out_of_memory(void)
|
||||
deque_push_back(me, &i);
|
||||
}
|
||||
assert(deque_size(me) == 32);
|
||||
fail_calloc = 1;
|
||||
fail_malloc = 1;
|
||||
assert(deque_clear(me) == -ENOMEM);
|
||||
for (i = 0; i < 32; i++) {
|
||||
int get = 0xfacade;
|
||||
@@ -351,6 +352,7 @@ static void test_clear_out_of_memory(void)
|
||||
}
|
||||
assert(deque_size(me) == 32);
|
||||
fail_malloc = 1;
|
||||
delay_fail_malloc = 1;
|
||||
assert(deque_clear(me) == -ENOMEM);
|
||||
for (i = 0; i < 32; i++) {
|
||||
int get = 0xfacade;
|
||||
@@ -461,7 +463,7 @@ static void test_big_object(void)
|
||||
assert(!deque_destroy(me));
|
||||
}
|
||||
|
||||
void test_add_all(int big_arr_size)
|
||||
static void test_add_all(int big_arr_size)
|
||||
{
|
||||
int i;
|
||||
double small_array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
@@ -489,7 +491,7 @@ void test_add_all(int big_arr_size)
|
||||
free(big_array);
|
||||
}
|
||||
|
||||
void test_add_all_failure(void)
|
||||
static void test_add_all_failure(void)
|
||||
{
|
||||
const size_t big_arr_size = 2000;
|
||||
size_t i;
|
||||
@@ -529,6 +531,80 @@ void test_add_all_failure(void)
|
||||
free(big_array);
|
||||
}
|
||||
|
||||
static void test_block_reuse_forwards(void)
|
||||
{
|
||||
size_t i;
|
||||
size_t queue_size = 1500;
|
||||
deque queue = deque_init(sizeof(double));
|
||||
for (i = 0; i < queue_size; i++) {
|
||||
double d = i;
|
||||
assert(deque_push_back(queue, &d) == BK_OK);
|
||||
}
|
||||
for (i = 0; i < queue_size; i++) {
|
||||
double d = i;
|
||||
double get;
|
||||
assert(deque_push_back(queue, &d) == BK_OK);
|
||||
assert(deque_pop_front(&get, queue) == BK_OK);
|
||||
assert(get == d);
|
||||
}
|
||||
deque_destroy(queue);
|
||||
}
|
||||
|
||||
static void test_block_reuse_backwards(void)
|
||||
{
|
||||
size_t i;
|
||||
size_t queue_size = 1500;
|
||||
deque queue = deque_init(sizeof(double));
|
||||
for (i = 0; i < queue_size; i++) {
|
||||
double d = i;
|
||||
assert(deque_push_front(queue, &d) == BK_OK);
|
||||
}
|
||||
for (i = 0; i < queue_size; i++) {
|
||||
double d = i;
|
||||
double get;
|
||||
assert(deque_push_front(queue, &d) == BK_OK);
|
||||
assert(deque_pop_back(&get, queue) == BK_OK);
|
||||
assert(get == d);
|
||||
}
|
||||
deque_destroy(queue);
|
||||
}
|
||||
|
||||
static void test_trim_both_sides(void)
|
||||
{
|
||||
int i;
|
||||
deque me = deque_init(sizeof(int));
|
||||
for (i = 999; i >= 0; i--) {
|
||||
assert(deque_push_front(me, &i) == BK_OK);
|
||||
assert(deque_push_back(me, &i) == BK_OK);
|
||||
}
|
||||
assert(deque_size(me) == 2000);
|
||||
assert(deque_trim(me) == BK_OK);
|
||||
assert(deque_size(me) == 2000);
|
||||
for (i = 0; i < 500; i++) {
|
||||
int get = 0xfacade;
|
||||
assert(deque_pop_front(&get, me) == BK_OK);
|
||||
assert(get == i);
|
||||
get = 0xfacade;
|
||||
assert(deque_pop_back(&get, me) == BK_OK);
|
||||
assert(get == i);
|
||||
}
|
||||
assert(deque_size(me) == 1000);
|
||||
assert(deque_trim(me) == BK_OK);
|
||||
assert(deque_size(me) == 1000);
|
||||
for (i = 500; i < 1000; i++) {
|
||||
int get = 0xfacade;
|
||||
assert(deque_pop_front(&get, me) == BK_OK);
|
||||
assert(get == i);
|
||||
get = 0xfacade;
|
||||
assert(deque_pop_back(&get, me) == BK_OK);
|
||||
assert(get == i);
|
||||
}
|
||||
assert(deque_size(me) == 0);
|
||||
assert(deque_trim(me) == BK_OK);
|
||||
assert(deque_size(me) == 0);
|
||||
deque_destroy(me);
|
||||
}
|
||||
|
||||
void test_deque(void)
|
||||
{
|
||||
int i;
|
||||
@@ -555,4 +631,7 @@ void test_deque(void)
|
||||
test_add_all(i);
|
||||
}
|
||||
test_add_all_failure();
|
||||
test_block_reuse_forwards();
|
||||
test_block_reuse_backwards();
|
||||
test_trim_both_sides();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user