From 00685b9ce9e6d89edcaffc9efa5352ea668df76f Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Tue, 18 Apr 2017 23:38:52 +1000 Subject: [PATCH] Add better push buffer system for dynamic alloc --- dqn.h | 184 +++++++++++++++++++++++++++++++++++----------- dqn_unit_test.cpp | 119 ++++++++++++++++++++++++++---- 2 files changed, 249 insertions(+), 54 deletions(-) diff --git a/dqn.h b/dqn.h index d06ac3e..1527913 100644 --- a/dqn.h +++ b/dqn.h @@ -100,7 +100,8 @@ bool dqn_array_init(DqnArray *array, size_t capacity) if (!dqn_array_free(array)) return false; } - array->data = (T *)calloc((size_t)capacity, sizeof(T)); + array->data = + (T *)dqn_mem_alloc_internal((size_t)capacity * sizeof(T), true); if (!array->data) return false; array->count = 0; @@ -117,7 +118,8 @@ bool dqn_array_grow(DqnArray *array) size_t newCapacity = (size_t)(array->capacity * GROWTH_FACTOR); if (newCapacity == array->capacity) newCapacity++; - T *newMem = (T *)realloc(array->data, (size_t)(newCapacity * sizeof(T))); + T *newMem = (T *)dqn_mem_realloc_internal( + array->data, (size_t)(newCapacity * sizeof(T))); if (newMem) { array->data = newMem; @@ -480,25 +482,37 @@ DQN_FILE_SCOPE i32 dqn_rnd_pcg_range(DqnRandPCGState *pcg, i32 min, i32 max); //////////////////////////////////////////////////////////////////////////////// // PushBuffer Header //////////////////////////////////////////////////////////////////////////////// -typedef struct PushBuffer +typedef struct DqnPushBufferBlock { u8 *memory; size_t used; size_t size; - i32 tempBufferCount; -} PushBuffer; -typedef struct TempBuffer + DqnPushBufferBlock *prevBlock; +} DqnPushBufferBlock; + +typedef struct DqnPushBuffer { - PushBuffer *buffer; + DqnPushBufferBlock *block; + + i32 tempBufferCount; + u32 alignment; +} DqnPushBuffer; + +typedef struct DqnTempBuffer +{ + DqnPushBuffer *buffer; + DqnPushBufferBlock *startingBlock; size_t used; -} TempBuffer; -DQN_FILE_SCOPE bool push_buffer_init (PushBuffer *buffer, void *memory, size_t size); -DQN_FILE_SCOPE void *push_buffer_allocate(PushBuffer *buffer, size_t size); +} DqnTempBuffer; -DQN_FILE_SCOPE TempBuffer push_buffer_begin_temp_region(PushBuffer *buffer); -DQN_FILE_SCOPE void push_buffer_end_temp_region (TempBuffer tempBuffer); +DQN_FILE_SCOPE bool dqn_push_buffer_init (DqnPushBuffer *buffer, size_t size, u32 alignment); +DQN_FILE_SCOPE void *dqn_push_buffer_allocate(DqnPushBuffer *buffer, size_t size); +DQN_FILE_SCOPE void dqn_push_buffer_free_last_buffer(DqnPushBuffer *buffer); + +DQN_FILE_SCOPE DqnTempBuffer dqn_push_buffer_begin_temp_region(DqnPushBuffer *buffer); +DQN_FILE_SCOPE void dqn_push_buffer_end_temp_region (DqnTempBuffer tempBuffer); #endif /* DQN_H */ @@ -1036,6 +1050,41 @@ STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char comma, char peri // NOTE: DQN_INI_IMPLEMENTATION modified to be included when DQN_IMPLEMENTATION defined // #define DQN_INI_IMPLEMENTATION #define DQN_INI_STRLEN(s) dqn_strlen(s) +//////////////////////////////////////////////////////////////////////////////// +// Memory +//////////////////////////////////////////////////////////////////////////////// +// NOTE: All memory allocations in dqn.h go through these functions. So they can +// be rerouted fairly easily especially for platform specific mallocs. +FILE_SCOPE void *dqn_mem_alloc_internal(size_t size, bool zeroClear) +{ + void *result = NULL; + + if (zeroClear) + { + result = calloc(1, size); + } + else + { + result = malloc(size); + } + return result; +} + +FILE_SCOPE void *dqn_mem_realloc_internal(void *memory, size_t newSize) +{ + void *result = realloc(memory, newSize); + return result; +} + +FILE_SCOPE void dqn_mem_free_internal(void *memory) +{ + if (memory) + { + free(memory); + memory = NULL; + } +} + //////////////////////////////////////////////////////////////////////////////// // Math //////////////////////////////////////////////////////////////////////////////// @@ -1159,7 +1208,7 @@ DQN_FILE_SCOPE inline f32 dqn_v2_length(DqnV2 a, DqnV2 b) DQN_FILE_SCOPE inline DqnV2 dqn_v2_normalise(DqnV2 a) { f32 magnitude = dqn_v2_length(dqn_v2(0, 0), a); - DqnV2 result = dqn_v2(a.x, a.y); + DqnV2 result = dqn_v2(a.x, a.y); result = dqn_v2_scale(a, 1 / magnitude); return result; } @@ -2155,25 +2204,26 @@ DQN_FILE_SCOPE char **dqn_dir_read(char *dir, u32 *numFiles) return NULL; } - - char **list = (char **)calloc(1, sizeof(*list) * (currNumFiles)); + char **list = (char **)dqn_mem_alloc_internal( + sizeof(*list) * (currNumFiles), true); if (!list) { - DQN_WIN32_ERROR_BOX("calloc() failed.", NULL); + DQN_WIN32_ERROR_BOX("dqn_mem_alloc_internal() failed.", NULL); return NULL; } for (u32 i = 0; i < currNumFiles; i++) { - list[i] = (char *)calloc(1, sizeof(**list) * MAX_PATH); + list[i] = + (char *)dqn_mem_alloc_internal(sizeof(**list) * MAX_PATH, true); if (!list[i]) { for (u32 j = 0; j < i; j++) { - free(list[j]); + dqn_mem_free_internal(list[j]); } - DQN_WIN32_ERROR_BOX("calloc() failed.", NULL); + DQN_WIN32_ERROR_BOX("dqn_mem_alloc_internal() failed.", NULL); return NULL; } } @@ -2200,11 +2250,11 @@ DQN_FILE_SCOPE inline void dqn_dir_read_free(char **fileList, u32 numFiles) { for (u32 i = 0; i < numFiles; i++) { - if (fileList[i]) free(fileList[i]); + if (fileList[i]) dqn_mem_free_internal(fileList[i]); fileList[i] = NULL; } - free(fileList); + dqn_mem_free_internal(fileList); } } @@ -2324,47 +2374,99 @@ DQN_FILE_SCOPE i32 dqn_rnd_pcg_range(DqnRandPCGState *pcg, i32 min, i32 max) } //////////////////////////////////////////////////////////////////////////////// -// PushBuffer Header +// DqnPushBuffer Header //////////////////////////////////////////////////////////////////////////////// -DQN_FILE_SCOPE bool push_buffer_init(PushBuffer *buffer, void *memory, size_t size) +FILE_SCOPE size_t inline dqn_size_alignment_internal(u32 alignment, size_t size) { - if (!buffer || !memory || size <= 0) return false; + size_t result = ((size + (alignment-1)) & ~(alignment-1)); + return result; +} + +FILE_SCOPE DqnPushBufferBlock * +dqn_push_buffer_alloc_block_internal(u32 alignment, size_t size) +{ + size_t alignedSize = dqn_size_alignment_internal(alignment, size); + size_t totalSize = alignedSize + sizeof(DqnPushBufferBlock); + + DqnPushBufferBlock *result = (DqnPushBufferBlock *)dqn_mem_alloc_internal(totalSize, true); + if (!result) return NULL; + + result->memory = (u8 *)result + sizeof(*result); + result->size = alignedSize; + result->used = 0; + return result; +} + +DQN_FILE_SCOPE bool dqn_push_buffer_init(DqnPushBuffer *buffer, size_t size, u32 alignment) +{ + if (!buffer || size <= 0) return false; + + buffer->block = dqn_push_buffer_alloc_block_internal(alignment, size); + if (!buffer->block) return false; - buffer->memory = (u8 *)memory; - buffer->size = size; - buffer->used = 0; buffer->tempBufferCount = 0; + buffer->alignment = alignment; return true; } -DQN_FILE_SCOPE inline void *push_buffer_allocate(PushBuffer *buffer, size_t size) +DQN_FILE_SCOPE void *dqn_push_buffer_allocate(DqnPushBuffer *buffer, size_t size) { - DQN_ASSERT((buffer->used + size) <= buffer->size); - void *result = buffer->memory + buffer->used; - buffer->used += size; + size_t alignedSize = dqn_size_alignment_internal(buffer->alignment, size); + if ((buffer->block->used + alignedSize) > buffer->block->size) + { + size_t newBlockSize = DQN_MAX(alignedSize, buffer->block->size); + DqnPushBufferBlock *newBlock = dqn_push_buffer_alloc_block_internal( + buffer->alignment, newBlockSize); + if (!newBlock) return NULL; + + newBlock->prevBlock = buffer->block; + buffer->block = newBlock; + } + + u8 *currPointer = buffer->block->memory + buffer->block->used; + u8 *alignedResult = (u8 *)dqn_size_alignment_internal(buffer->alignment, (size_t)currPointer); + size_t alignmentOffset = (size_t)(alignedResult - currPointer); + + void *result = alignedResult; + buffer->block->used += (alignedSize + alignmentOffset); return result; } -DQN_FILE_SCOPE TempBuffer push_buffer_begin_temp_region(PushBuffer *buffer) +DQN_FILE_SCOPE void dqn_push_buffer_free_last_buffer(DqnPushBuffer *buffer) { - TempBuffer result = {}; - result.buffer = buffer; - result.used = buffer->used; + DqnPushBufferBlock *prevBlock = buffer->block->prevBlock; + dqn_mem_free_internal(buffer->block); + buffer->block = prevBlock; + + // No more blocks, then last block has been freed + if (!buffer->block) DQN_ASSERT(buffer->tempBufferCount == 0); +} + +DQN_FILE_SCOPE DqnTempBuffer dqn_push_buffer_begin_temp_region(DqnPushBuffer *buffer) +{ + DqnTempBuffer result = {}; + result.buffer = buffer; + result.startingBlock = buffer->block; + result.used = buffer->block->used; buffer->tempBufferCount++; - return result; } -DQN_FILE_SCOPE void push_buffer_end_temp_region(TempBuffer tempBuffer) +DQN_FILE_SCOPE void dqn_push_buffer_end_temp_region(DqnTempBuffer tempBuffer) { - PushBuffer *buffer = tempBuffer.buffer; - DQN_ASSERT(buffer->used > tempBuffer.used) + DqnPushBuffer *buffer = tempBuffer.buffer; + while (buffer->block != tempBuffer.startingBlock) + dqn_push_buffer_free_last_buffer(buffer); - buffer->used = tempBuffer.used; + if (buffer->block) + { + DQN_ASSERT(buffer->block->used >= tempBuffer.used); + buffer->block->used = tempBuffer.used; + DQN_ASSERT(buffer->tempBufferCount >= 0); + } buffer->tempBufferCount--; - DQN_ASSERT(buffer->tempBufferCount >= 0); } //////////////////////////////////////////////////////////////////////////////// diff --git a/dqn_unit_test.cpp b/dqn_unit_test.cpp index d1fc9a2..05653f9 100644 --- a/dqn_unit_test.cpp +++ b/dqn_unit_test.cpp @@ -815,22 +815,115 @@ void dqn_file_test() void dqn_push_buffer_test() { - size_t blockSize = DQN_KILOBYTE(1); - void *block = calloc(1, blockSize); - PushBuffer buffer = {}; - push_buffer_init(&buffer, block, blockSize); + size_t allocSize = DQN_KILOBYTE(1); + DqnPushBuffer buffer = {}; + const u32 ALIGNMENT = 4; + dqn_push_buffer_init(&buffer, allocSize, ALIGNMENT); + DQN_ASSERT(buffer.block && buffer.block->memory); + DQN_ASSERT(buffer.block->size == allocSize); + DQN_ASSERT(buffer.block->used == 0); + DQN_ASSERT(buffer.alignment == ALIGNMENT); - DQN_ASSERT(buffer.memory == block); - DQN_ASSERT(buffer.size == blockSize); - DQN_ASSERT(buffer.used == 0); + // Alocate A + size_t sizeA = (size_t)(allocSize * 0.5f); + void *resultA = dqn_push_buffer_allocate(&buffer, sizeA); + u64 resultAddrA = *((u64 *)resultA); + DQN_ASSERT(resultAddrA % ALIGNMENT == 0); + DQN_ASSERT(buffer.block && buffer.block->memory); + DQN_ASSERT(buffer.block->size == allocSize); + DQN_ASSERT(buffer.block->used >= sizeA + 0 && + buffer.block->used <= sizeA + 3); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + DQN_ASSERT(resultA); + u8 *ptrA = (u8 *)resultA; + for (u32 i = 0; i < sizeA; i++) + ptrA[i] = 1; - void *result = push_buffer_allocate(&buffer, (size_t)(blockSize * 0.5f)); - DQN_ASSERT(buffer.memory == block); - DQN_ASSERT(buffer.size == blockSize); - DQN_ASSERT(buffer.used == (size_t)(blockSize * 0.5f)); - DQN_ASSERT(result); + DqnPushBufferBlock *blockA = buffer.block; + // Alocate B + size_t sizeB = (size_t)(allocSize * 2.0f); + void *resultB = dqn_push_buffer_allocate(&buffer, sizeB); + u64 resultAddrB = *((u64 *)resultB); + DQN_ASSERT(resultAddrB % ALIGNMENT == 0); + DQN_ASSERT(buffer.block && buffer.block->memory); + DQN_ASSERT(buffer.block->size == DQN_KILOBYTE(2)); - free(block); + // Since we alignment the pointers we return they can be within 0-3 bytes of + // what we expect and since this is in a new block as well used will reflect + // just this allocation. + DQN_ASSERT(buffer.block->used >= sizeB + 0 && + buffer.block->used <= sizeB + 3); + DQN_ASSERT(resultB); + u8 *ptrB = (u8 *)resultB; + for (u32 i = 0; i < sizeB; i++) + ptrB[i] = 2; + + // Check that a new block was created since there wasn't enough space + DQN_ASSERT(buffer.block->prevBlock == blockA); + DQN_ASSERT(buffer.block != blockA); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + DQN_ASSERT(blockA->used == sizeA); + DqnPushBufferBlock *blockB = buffer.block; + + // Check temp regions work + DqnTempBuffer tempBuffer = dqn_push_buffer_begin_temp_region(&buffer); + size_t sizeC = 1024 + 1; + void *resultC = dqn_push_buffer_allocate(tempBuffer.buffer, sizeC); + u64 resultAddrC = *((u64 *)resultC); + DQN_ASSERT(resultAddrC % ALIGNMENT == 0); + DQN_ASSERT(buffer.block != blockB && buffer.block != blockA); + DQN_ASSERT(buffer.block->used >= sizeC + 0 && + buffer.block->used <= sizeC + 3); + DQN_ASSERT(buffer.tempBufferCount == 1); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + + // NOTE: Allocation should be aligned to 4 byte boundary + DQN_ASSERT(tempBuffer.buffer->block->size == 2048); + u8 *ptrC = (u8 *)resultC; + for (u32 i = 0; i < sizeC; i++) + ptrC[i] = 3; + + // Check that a new block was created since there wasn't enough space + DQN_ASSERT(buffer.block->prevBlock == blockB); + DQN_ASSERT(buffer.block != blockB); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + + for (u32 i = 0; i < sizeA; i++) + DQN_ASSERT(ptrA[i] == 1); + for (u32 i = 0; i < sizeB; i++) + DQN_ASSERT(ptrB[i] == 2); + for (u32 i = 0; i < sizeC; i++) + DQN_ASSERT(ptrC[i] == 3); + + // End temp region which should revert back to 2 linked buffers, A and B + dqn_push_buffer_end_temp_region(tempBuffer); + DQN_ASSERT(buffer.block && buffer.block->memory); + DQN_ASSERT(buffer.block->size == sizeB); + DQN_ASSERT(buffer.block->used >= sizeB + 0 && + buffer.block->used <= sizeB + 3); + DQN_ASSERT(buffer.tempBufferCount == 0); + DQN_ASSERT(resultB); + + DQN_ASSERT(buffer.block->prevBlock == blockA); + DQN_ASSERT(buffer.block != blockA); + DQN_ASSERT(blockA->used == sizeA); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + + // Release the last linked buffer from the push buffer + dqn_push_buffer_free_last_buffer(&buffer); + + // Which should return back to the 1st allocation + DQN_ASSERT(buffer.block == blockA); + DQN_ASSERT(buffer.block->memory); + DQN_ASSERT(buffer.block->size == allocSize); + DQN_ASSERT(buffer.block->used == sizeA); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + + // Free once more to release buffer A memory + dqn_push_buffer_free_last_buffer(&buffer); + DQN_ASSERT(!buffer.block); + DQN_ASSERT(buffer.alignment == ALIGNMENT); + DQN_ASSERT(buffer.tempBufferCount == 0); } int main(void)