Add quicksort generic to lib

This commit is contained in:
Doyle Thai 2017-06-28 22:47:27 +10:00
parent 825caefe4a
commit cbf7b4d606
3 changed files with 335 additions and 18 deletions

View File

@ -17,7 +17,7 @@ REM Drop compilation files into build folder
IF NOT EXIST bin mkdir bin
pushd bin
REM EHa- disable exception handling (we don't use)
REM EHa- disable exception handling (but we use for algorithms so /EHsc)
REM GR- disable c runtime type information (we don't use)
REM MD use dynamic runtime library
@ -30,11 +30,8 @@ REM Zi enables debug data, Z7 combines the debug files into one.
REM W4 warning level 4
REM WX treat warnings as errors
REM wd4100 ignore: unused argument parameters
REM wd4201 ignore: nonstandard extension used: nameless struct/union
REM wd4189 ignore: local variable is initialised but not referenced
set CompileFlags=-EHa- -GR- -Oi -MT -Z7 -W4 -WX -wd4100 -wd4201 -wd4189 -FC -Od
set CompileFlags=-EHsc -GR- -Oi -MT -Z7 -W4 -WX -wd4201 -FC -O2
REM Include directories
set IncludeFlags=
@ -44,7 +41,7 @@ set LinkLibraries=user32.lib gdi32.lib msimg32.lib
REM incrmenetal:no, turn incremental builds off
REM opt:ref, try to remove functions from libs that are referenced at all
set LinkFlags=-incremental:no -opt:ref
set LinkFlags=-incremental:no -opt:ref -machine:x64
cl %CompileFlags% %CompileEntryPoint% %IncludeFlags% /link %LinkLibraries% %LinkFlags% /nologo /OUT:"%ProjectName%.exe"
REM cl /P /c %CompileFlags% %CompileEntryPoint%

189
dqn.h
View File

@ -42,6 +42,7 @@
// #DqnWChar WChar Operations (IsDigit(), IsAlpha() etc)
// #DqnWStr WStr Operations (WStr_Len() etc)
// #DqnRnd Random Number Generator (ints and floats)
// #Dqn_* Dqn_QuickSort
// #XPlatform (Win32 & Unix)
// #DqnFile File I/O (Read, Write, Delete)
@ -61,8 +62,14 @@
// #DqnSprintf Cross-platform Sprintf Implementation (Public Domain lib stb_sprintf)
// TODO
// - DqnMemStack
// - Allow 0 size memblock stack initialisation/block-less stack for situations where you don't
// care about specifying a size upfront
//
// - Win32
// - Get rid of reliance on MAX_PATH
//
// - Make lib compile and run on Linux with GCC using -03
////////////////////////////////////////////////////////////////////////////////
// Preprocessor Checks
@ -972,6 +979,90 @@ DQN_FILE_SCOPE u32 DqnRnd_PCGNext (DqnRandPCGState *pcg);
DQN_FILE_SCOPE f32 DqnRnd_PCGNextf(DqnRandPCGState *pcg);
// return: A random integer N between [min, max]
DQN_FILE_SCOPE i32 DqnRnd_PCGRange(DqnRandPCGState *pcg, i32 min, i32 max);
////////////////////////////////////////////////////////////////////////////////
// #Dqn_* Public API
////////////////////////////////////////////////////////////////////////////////
typedef bool Dqn_QuickSortLessThanCallback(const void *const val1, const void *const val2);
typedef void Dqn_QuickSortSwapCallback (void *const val1, void *const val2);
DQN_FILE_SCOPE void Dqn_QuickSortC(void *const array, const u32 itemSize, const u32 size,
Dqn_QuickSortLessThanCallback *const IsLessThan,
Dqn_QuickSortSwapCallback *const Swap);
template <typename T>
DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const u32 size,
Dqn_QuickSortLessThanCallback *const IsLessThan)
{
if (!array || size == 0 || size == 1 || !IsLessThan) return;
// Insertion Sort, under 24->32 is an optimal amount
const u32 QUICK_SORT_THRESHOLD = 24;
if (size < QUICK_SORT_THRESHOLD)
{
u32 itemToInsertIndex = 1;
while (itemToInsertIndex < size)
{
for (u32 checkIndex = 0; checkIndex < itemToInsertIndex; checkIndex++)
{
if (!IsLessThan(&array[checkIndex], &array[itemToInsertIndex]))
{
T itemToInsert = array[itemToInsertIndex];
for (u32 i = itemToInsertIndex; i > checkIndex; i--)
array[i] = array[i - 1];
array[checkIndex] = itemToInsert;
break;
}
}
itemToInsertIndex++;
}
return;
}
DqnRandPCGState state = {};
DqnRnd_PCGInit(&state);
u32 pivotIndex = DqnRnd_PCGRange(&state, 0, size - 1);
u32 partitionIndex = 0;
u32 startIndex = 0;
// Swap pivot with last index, so pivot is always at the end of the array.
// This makes logic much simpler.
{
u32 lastIndex = size - 1;
DQN_SWAP(T, array[lastIndex], array[pivotIndex]);
pivotIndex = lastIndex;
}
// 4^, 8, 7, 5, 2, 3, 6
if (IsLessThan(&array[startIndex], &array[pivotIndex])) partitionIndex++;
startIndex++;
// 4, |8, 7, 5^, 2, 3, 6*
// 4, 5, |7, 8, 2^, 3, 6*
// 4, 5, 2, |8, 7, ^3, 6*
// 4, 5, 2, 3, |7, 8, ^6*
for (u32 checkIndex = startIndex; checkIndex < size; checkIndex++)
{
if (IsLessThan(&array[checkIndex], &array[pivotIndex]))
{
DQN_SWAP(T, array[partitionIndex], array[checkIndex]);
partitionIndex++;
}
}
// Move pivot to right of partition
// 4, 5, 2, 3, |6, 8, ^7*
DQN_SWAP(T, array[partitionIndex], array[pivotIndex]);
Dqn_QuickSort(array, partitionIndex, IsLessThan);
// Skip the value at partion index since that is guaranteed to be sorted.
// 4, 5, 2, 3, (x), 8, 7
u32 oneAfterPartitionIndex = partitionIndex + 1;
Dqn_QuickSort(array + oneAfterPartitionIndex, (size - oneAfterPartitionIndex), IsLessThan);
}
#endif /* DQN_H */
////////////////////////////////////////////////////////////////////////////////
@ -1899,9 +1990,10 @@ DqnMemStackInternal_AllocateBlock(u32 byteAlign, size_t size, const bool zeroCle
if (!result) return NULL;
result->memory = (u8 *)DQN_ALIGN_POW_N((u8 *)result + sizeof(*result), byteAlign);
result->size = alignedSize;
result->used = 0;
result->memory = (u8 *)DQN_ALIGN_POW_N((u8 *)result + sizeof(*result), byteAlign);
result->size = alignedSize;
result->used = 0;
result->prevBlock = NULL;
return result;
}
@ -1966,6 +2058,8 @@ DQN_FILE_SCOPE bool DqnMemStack_InitWithFixedMem(DqnMemStack *const stack, u8 *c
const u32 DEFAULT_ALIGNMENT = 4;
stack->tempRegionCount = 0;
stack->byteAlign = (byteAlign == 0) ? DEFAULT_ALIGNMENT : byteAlign;
DQN_ASSERT(!stack->block->prevBlock);
return true;
}
@ -1976,6 +2070,7 @@ DQN_FILE_SCOPE bool DqnMemStack_InitWithFixedSize(DqnMemStack *const stack, size
if (result)
{
stack->flags |= DqnMemStackFlag_IsNotExpandable;
DQN_ASSERT(!stack->block->prevBlock);
return true;
}
@ -1996,6 +2091,7 @@ DQN_FILE_SCOPE bool DqnMemStack_Init(DqnMemStack *const stack, size_t size, cons
stack->tempRegionCount = 0;
stack->byteAlign = byteAlign;
stack->flags = 0;
DQN_ASSERT(!stack->block->prevBlock);
return true;
}
@ -2067,6 +2163,10 @@ DQN_FILE_SCOPE bool DqnMemStack_Pop(DqnMemStack *const stack, void *ptr, size_t
if (DQN_ASSERT_MSG(calcSize == sizeAligned, "'ptr' was not the last item allocated to memStack"))
{
stack->block->used -= sizeAligned;
if (stack->block->used == 0 && stack->block->prevBlock)
{
return DQN_ASSERT(DqnMemStack_FreeLastBlock(stack));
}
return true;
}
}
@ -3927,6 +4027,65 @@ DQN_FILE_SCOPE i32 DqnRnd_PCGRange(DqnRandPCGState *pcg, i32 min, i32 max)
return min + value;
}
////////////////////////////////////////////////////////////////////////////////
// #Dqn_* Implementation
////////////////////////////////////////////////////////////////////////////////
FILE_SCOPE void *DqnInternal_QuickSortGetArrayItemPtr(void *const array, const u32 index,
const u32 size)
{
u8 *byteArray = (u8 *)array;
void *result = (void *)(byteArray + (index * size));
return result;
}
DQN_FILE_SCOPE void Dqn_QuickSortC(void *const array, const u32 itemSize, const u32 size,
Dqn_QuickSortLessThanCallback *const IsLessThan,
Dqn_QuickSortSwapCallback *const Swap)
{
// NOTE: See <template> version for better implementation comments since here we need to use
// void * for generics in C making it harder in general to comprehend.
if (!array || size == 0 || size == 1) return;
DqnRandPCGState state = {};
DqnRnd_PCGInit(&state);
u32 pivotIndex = DqnRnd_PCGRange(&state, 0, size - 1);
u32 partitionIndex = 0;
u32 startIndex = 0;
{
void *lastItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, size - 1, itemSize);
void *pivotItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, pivotIndex, itemSize);
Swap(lastItemPtr, pivotItemPtr);
pivotIndex = size - 1;
}
void *startItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, startIndex, itemSize);
void *pivotItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, pivotIndex, itemSize);
if (IsLessThan(startItemPtr, pivotItemPtr)) partitionIndex++;
startIndex++;
for (u32 checkIndex = startIndex; checkIndex < size; checkIndex++)
{
void *checkItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, checkIndex, itemSize);
if (IsLessThan(checkItemPtr, pivotItemPtr))
{
void *partitionItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, partitionIndex, itemSize);
Swap(partitionItemPtr, checkItemPtr);
partitionIndex++;
}
}
void *partitionItemPtr = DqnInternal_QuickSortGetArrayItemPtr(array, partitionIndex, itemSize);
Swap(partitionItemPtr, pivotItemPtr);
Dqn_QuickSortC(array, itemSize, partitionIndex, IsLessThan, Swap);
u32 oneAfterPartitionIndex = partitionIndex + 1;
void *oneAfterPartionIndexPtr = DqnInternal_QuickSortGetArrayItemPtr(array, oneAfterPartitionIndex, itemSize);
Dqn_QuickSortC(oneAfterPartionIndexPtr, itemSize, (size - oneAfterPartitionIndex), IsLessThan, Swap);
}
////////////////////////////////////////////////////////////////////////////////
// #External Code
////////////////////////////////////////////////////////////////////////////////
@ -5639,11 +5798,24 @@ void DqnIni_PropertyValueSet(DqnIni *ini, int section, int property,
#include <unistd.h> // unlink()
#endif
#ifdef DQN_CPP_MODE
////////////////////////////////////////////////////////////////////////////////
// XPlatform > #DqnFile CPP Implementation
////////////////////////////////////////////////////////////////////////////////
DqnFile::DqnFile (const bool raiiCleanup) { this->raiiCleanup = raiiCleanup; }
DqnFile::~DqnFile() { if (this->raiiCleanup) this->Close(); }
// NOTE(doyle): This is necessary since we supply a default constructor, some uninitialised values
// were put in when using DqnFile file = {};
DqnFile::DqnFile(const bool raiiCleanup)
: permissionFlags(0)
, handle(0)
, size(0)
, raiiCleanup(raiiCleanup)
{
}
DqnFile::~DqnFile()
{
if (this->raiiCleanup) this->Close();
}
bool DqnFile::Open(const char *const path, const u32 permissionFlags_,
const enum DqnFileAction action)
@ -5666,8 +5838,8 @@ size_t DqnFile::Read(u8 *const buffer, const size_t numBytesToRead)
{
return DqnFile_Read(this, buffer, numBytesToRead);
}
void DqnFile::Close() { DqnFile_Close(this); }
#endif
////////////////////////////////////////////////////////////////////////////////
// XPlatform > #DqnFileInternal Implementation
@ -6167,7 +6339,7 @@ DQN_FILE_SCOPE bool DqnFile_GetFileSizeW(const wchar_t *const path, size_t *cons
LARGE_INTEGER largeInt = {};
largeInt.HighPart = attribData.nFileSizeHigh;
largeInt.LowPart = attribData.nFileSizeLow;
*size = largeInt.QuadPart;
*size = (size_t)largeInt.QuadPart;
return true;
}
@ -6308,8 +6480,7 @@ DQN_FILE_SCOPE f64 DqnTimer_NowInMs()
}
else
{
printf("tv_nsec: %ld\n", timeSpec.tv_nsec);
result = ((f64)timeSpec.tv_sec * 1000.0f) + ((f64)timeSpec.tv_nsec / 100000.0f);
result = (f64)((timeSpec.tv_sec * 1000.0f) + (timeSpec.tv_nsec / 1000000.0f));
}
#else

View File

@ -17,6 +17,18 @@
#include <limits.h>
#include <stdio.h>
FILE_SCOPE void PrintHeader(const char *const header)
{
DQN_ASSERT_HARD(header);
char buf[1024] = {};
DQN_ASSERT(Dqn_sprintf(buf, "// %s", header) < (i32)DQN_ARRAY_COUNT(buf));
printf("//////////////////////////////////////////////////////////////////\n");
printf("%s\n", buf);
printf("//////////////////////////////////////////////////////////////////\n");
}
void HandmadeMathVerifyMat4(DqnMat4 dqnMat, hmm_mat4 hmmMat)
{
f32 *hmmMatf = (f32 *)&hmmMat;
@ -40,6 +52,7 @@ void HandmadeMathVerifyMat4(DqnMat4 dqnMat, hmm_mat4 hmmMat)
void HandmadeMathTest()
{
PrintHeader("DqnMath vs HandmadeMath Test");
// Test Perspective/Projection matrix values
if (1)
{
@ -114,6 +127,7 @@ void HandmadeMathTest()
void StringsTest()
{
PrintHeader("Strings Test");
// Char Checks
if (1)
{
@ -525,7 +539,7 @@ void StringsTest()
void OtherTest()
{
PrintHeader("Other Test");
if (1)
{
#if defined(DQN_UNIX_PLATFORM)
@ -552,7 +566,7 @@ void OtherTest()
void RandomTest()
{
PrintHeader("Random Number Generator Test");
DqnRandPCGState pcg;
DqnRnd_PCGInit(&pcg);
for (i32 i = 0; i < 10; i++)
@ -572,6 +586,7 @@ void RandomTest()
void MathTest()
{
PrintHeader("Math Test");
if (1)
{ // Lerp
if (1)
@ -604,6 +619,7 @@ void MathTest()
void VecTest()
{
PrintHeader("Math Vector Test");
if (1)
{ // V2
@ -964,6 +980,7 @@ void VecTest()
void ArrayTestMemAPIInternal(DqnArray<DqnV2> *array, DqnMemAPI memAPI)
{
PrintHeader("Array with Default Mem API Test");
if (1)
{
DQN_ASSERT(DqnArray_Init(array, 1, memAPI));
@ -1139,6 +1156,7 @@ void ArrayTestMemAPIInternal(DqnArray<DqnV2> *array, DqnMemAPI memAPI)
void ArrayTest()
{
PrintHeader("Array Test");
DqnArray<DqnV2> array = {};
ArrayTestMemAPIInternal(&array, DqnMemAPI_DefaultUseCalloc());
printf("ArrayTest(): Completed successfully\n");
@ -1146,6 +1164,7 @@ void ArrayTest()
void MemStackTest()
{
PrintHeader("MemStack Test");
// Test over allocation, alignments, temp regions
if (1)
{
@ -1250,6 +1269,7 @@ void MemStackTest()
DQN_ASSERT(stack.block->size == allocSize);
DQN_ASSERT(stack.block->used == sizeA);
DQN_ASSERT(stack.byteAlign == ALIGNMENT);
DQN_ASSERT(!stack.block->prevBlock);
// Free once more to release stack A memory
DqnMemStack_FreeLastBlock(&stack);
@ -1532,6 +1552,7 @@ void MemStackTest()
#ifdef DQN_XPLATFORM_LAYER
void FileTest()
{
PrintHeader("File Test");
// File i/o
if (1)
{
@ -1740,10 +1761,13 @@ const u32 QUEUE_SIZE = 256;
FILE_SCOPE void JobQueueDebugCallbackIncrementCounter(DqnJobQueue *const queue,
void *const userData)
{
(void)userData;
DQN_ASSERT(queue->size == QUEUE_SIZE);
{
DqnLockGuard guard = globalJobQueueLock.LockGuard();
globalDebugCounter++;
#if 0
u32 number = globalDebugCounter;
#if defined(DQN_WIN32_IMPLEMENTATION)
printf("JobQueueDebugCallbackIncrementCounter(): Thread %d: Incrementing Number: %d\n",
@ -1751,6 +1775,7 @@ FILE_SCOPE void JobQueueDebugCallbackIncrementCounter(DqnJobQueue *const queue,
#elif defined(DQN_UNIX_IMPLEMENTATION)
printf("JobQueueDebugCallbackIncrementCounter(): Thread unix: Incrementing Number: %d\n",
number);
#endif
#endif
}
@ -1758,6 +1783,7 @@ FILE_SCOPE void JobQueueDebugCallbackIncrementCounter(DqnJobQueue *const queue,
FILE_SCOPE void JobQueueTest()
{
PrintHeader("Job Queue Multithreading Test");
globalDebugCounter = 0;
DqnMemStack memStack = {};
@ -1788,11 +1814,133 @@ FILE_SCOPE void JobQueueTest()
DqnJobQueue_BlockAndCompleteAllJobs(&jobQueue);
printf("\nJobQueueTest(): Final incremented value: %d\n", globalDebugCounter);
printf("JobQueueTest(): Final incremented value: %d\n", globalDebugCounter);
DQN_ASSERT(globalDebugCounter == WORK_ENTRIES);
DqnLock_Delete(&globalJobQueueLock);
}
FILE_SCOPE inline bool Dqn_QuickSortLessThanU32(const void *const val1, const void *const val2)
{
const u32 *const a = (u32 *)val1;
const u32 *const b = (u32 *)val2;
return (*a) < (*b);
}
FILE_SCOPE inline void Dqn_QuickSortSwapU32(void *const val1, void *const val2)
{
u32 *a = (u32 *)val1;
u32 *b = (u32 *)val2;
DQN_SWAP(u32, *a, *b);
}
#include <algorithm>
void SortTest()
{
{
u32 array[] = {4, 8, 7, 5, 2, 3, 6};
Dqn_QuickSortC(array, sizeof(array[0]), DQN_ARRAY_COUNT(array), Dqn_QuickSortLessThanU32,
Dqn_QuickSortSwapU32);
for (u32 i = 0; i < DQN_ARRAY_COUNT(array) - 1; i++)
{
DQN_ASSERT(array[i] <= array[i + 1]);
}
}
PrintHeader("DqnSort vs std::Sort");
DqnRandPCGState state = {};
DqnRnd_PCGInit(&state);
if (1)
{
DqnMemStack stack = {};
DQN_ASSERT(stack.Init(DQN_KILOBYTE(1), false));
// Create array of ints
u32 numInts = 1000000;
u32 sizeInBytes = sizeof(u32) * numInts;
u32 *dqnCArray = (u32 *)stack.Push(sizeInBytes);
u32 *dqnCPPArray = (u32 *)stack.Push(sizeInBytes);
u32 *stdArray = (u32 *)stack.Push(sizeInBytes);
DQN_ASSERT(dqnCArray && dqnCPPArray && stdArray);
f64 dqnCTimings[10] = {};
f64 dqnCPPTimings[DQN_ARRAY_COUNT(dqnCTimings)] = {};
f64 stdTimings[DQN_ARRAY_COUNT(dqnCTimings)] = {};
f64 dqnCAverage = 0;
f64 dqnCPPAverage = 0;
f64 stdAverage = 0;
for (u32 timingsIndex = 0; timingsIndex < DQN_ARRAY_COUNT(dqnCTimings); timingsIndex++)
{
// Populate with random numbers
for (u32 i = 0; i < numInts; i++)
{
dqnCArray[i] = DqnRnd_PCGNext(&state);
dqnCPPArray[i] = dqnCArray[i];
stdArray[i] = dqnCPPArray[i];
}
// Time Dqn_QuickSortC
{
f64 start = DqnTimer_NowInS();
Dqn_QuickSortC(dqnCArray, sizeof(dqnCArray[0]), numInts, Dqn_QuickSortLessThanU32,
Dqn_QuickSortSwapU32);
f64 duration = DqnTimer_NowInS() - start;
dqnCTimings[timingsIndex] = duration;
dqnCAverage += duration;
printf("[%02d]Dqn_QuickSortC: %f vs ", timingsIndex, dqnCTimings[timingsIndex]);
}
// Time Dqn_QuickSortC
{
f64 start = DqnTimer_NowInS();
Dqn_QuickSort(dqnCPPArray, numInts, Dqn_QuickSortLessThanU32);
f64 duration = DqnTimer_NowInS() - start;
dqnCPPTimings[timingsIndex] = duration;
dqnCPPAverage += duration;
printf("Dqn_QuickSort: %f vs ", dqnCPPTimings[timingsIndex]);
}
// Time std::sort
{
f64 start = DqnTimer_NowInS();
std::sort(stdArray, stdArray + numInts);
f64 duration = DqnTimer_NowInS() - start;
stdTimings[timingsIndex] = duration;
stdAverage += duration;
printf("std::sort: %f\n", stdTimings[timingsIndex]);
}
for (u32 i = 0; i < numInts; i++)
{
DQN_ASSERT_MSG(dqnCArray[i] == stdArray[i], "DqnArray[%d]: %d, stdArray[%d]: %d", i,
dqnCArray[i], stdArray[i], i);
}
}
// Print averages
if (1)
{
dqnCAverage /= (f64)DQN_ARRAY_COUNT(dqnCTimings);
dqnCPPAverage /= (f64)DQN_ARRAY_COUNT(dqnCPPTimings);
stdAverage /= (f64)DQN_ARRAY_COUNT(stdTimings);
printf("\n- Average Timings\n");
printf(" Dqn_QuickSortC: %f vs Dqn_QuickSort: %f vs std::sort: %f\n\n", dqnCAverage,
dqnCPPAverage, stdAverage);
}
stack.Pop(stdArray, sizeInBytes);
stack.Pop(dqnCPPArray, sizeInBytes);
stack.Pop(dqnCArray, sizeInBytes);
stack.Free();
}
}
int main(void)
{
StringsTest();
@ -1802,6 +1950,7 @@ int main(void)
VecTest();
ArrayTest();
MemStackTest();
SortTest();
#ifdef DQN_XPLATFORM_LAYER
FileTest();