LibC: Cleanup qsort implementation
Remove random template and just make qsort be a wrapper around qsort_r Recurse only into the smaller partition. This removes the possibility of O(n) recursion in the worst case Use insertion sort for ranges shorter than 16 elements. Split qsort_swap into qsort_swap_fixed and qsort_swap_general - fixed one handles 1, 2, 4 and 8 byte elements which can be done directly with mov instructions - general copies 64 bytes at a time followed by a single copy for the rest. the 64 byte chunk loop gets nicely optimized into sse
This commit is contained in:
@@ -542,22 +542,51 @@ void* bsearch(const void* key, const void* base, size_t nel, size_t width, int (
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qsort_swap(void* lhs, void* rhs, size_t width)
|
template<typename T>
|
||||||
|
static inline void qsort_swap_fixed(void* lhs, void* rhs)
|
||||||
{
|
{
|
||||||
|
T temp;
|
||||||
|
memcpy(&temp, lhs, sizeof(T));
|
||||||
|
memcpy(lhs, rhs, sizeof(T));
|
||||||
|
memcpy(rhs, &temp, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qsort_swap_generic(void* lhs, void* rhs, size_t width)
|
||||||
|
{
|
||||||
|
uint8_t temp[64];
|
||||||
|
|
||||||
uint8_t* ulhs = static_cast<uint8_t*>(lhs);
|
uint8_t* ulhs = static_cast<uint8_t*>(lhs);
|
||||||
uint8_t* urhs = static_cast<uint8_t*>(rhs);
|
uint8_t* urhs = static_cast<uint8_t*>(rhs);
|
||||||
|
|
||||||
uint8_t temp[64];
|
while (width >= sizeof(temp))
|
||||||
while (width > 0)
|
|
||||||
{
|
{
|
||||||
const size_t to_swap = BAN::Math::min(width, sizeof(temp));
|
memcpy(temp, ulhs, sizeof(temp));
|
||||||
memcpy(temp, ulhs, to_swap);
|
memcpy(ulhs, urhs, sizeof(temp));
|
||||||
memcpy(ulhs, urhs, to_swap);
|
memcpy(urhs, temp, sizeof(temp));
|
||||||
memcpy(urhs, temp, to_swap);
|
width -= sizeof(temp);
|
||||||
width -= to_swap;
|
ulhs += sizeof(temp);
|
||||||
ulhs += to_swap;
|
urhs += sizeof(temp);
|
||||||
urhs += to_swap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (width > 0)
|
||||||
|
{
|
||||||
|
memcpy(temp, ulhs, width);
|
||||||
|
memcpy(ulhs, urhs, width);
|
||||||
|
memcpy(urhs, temp, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qsort_swap(void* lhs, void* rhs, size_t width)
|
||||||
|
{
|
||||||
|
switch (width)
|
||||||
|
{
|
||||||
|
case 1: return qsort_swap_fixed<uint8_t> (lhs, rhs);
|
||||||
|
case 2: return qsort_swap_fixed<uint16_t>(lhs, rhs);
|
||||||
|
case 4: return qsort_swap_fixed<uint32_t>(lhs, rhs);
|
||||||
|
case 8: return qsort_swap_fixed<uint64_t>(lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
qsort_swap_generic(lhs, rhs, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct qsort_pair
|
struct qsort_pair
|
||||||
@@ -566,8 +595,7 @@ struct qsort_pair
|
|||||||
uint8_t* gt;
|
uint8_t* gt;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Args>
|
static qsort_pair qsort_partition(uint8_t* pbegin, uint8_t* pend, size_t width, int (*compar)(const void*, const void*, void*), void* arg)
|
||||||
static qsort_pair qsort_partition(uint8_t* pbegin, uint8_t* pend, size_t width, int (*compar)(const void*, const void*, Args...), Args... args)
|
|
||||||
{
|
{
|
||||||
uint8_t* pivot = pbegin + (pend - pbegin) / width / 2 * width;
|
uint8_t* pivot = pbegin + (pend - pbegin) / width / 2 * width;
|
||||||
|
|
||||||
@@ -577,10 +605,11 @@ static qsort_pair qsort_partition(uint8_t* pbegin, uint8_t* pend, size_t width,
|
|||||||
|
|
||||||
while (eq < gt)
|
while (eq < gt)
|
||||||
{
|
{
|
||||||
const int comp = (eq == pivot) ? 0 : compar(eq, pivot, args...);
|
const int comp = (eq == pivot) ? 0 : compar(eq, pivot, arg);
|
||||||
|
|
||||||
if (comp < 0)
|
if (comp < 0)
|
||||||
{
|
{
|
||||||
|
if (eq != lt)
|
||||||
qsort_swap(eq, lt, width);
|
qsort_swap(eq, lt, width);
|
||||||
if (pivot == lt)
|
if (pivot == lt)
|
||||||
pivot = eq;
|
pivot = eq;
|
||||||
@@ -590,6 +619,7 @@ static qsort_pair qsort_partition(uint8_t* pbegin, uint8_t* pend, size_t width,
|
|||||||
else if (comp > 0)
|
else if (comp > 0)
|
||||||
{
|
{
|
||||||
gt -= width;
|
gt -= width;
|
||||||
|
if (eq != gt)
|
||||||
qsort_swap(eq, gt, width);
|
qsort_swap(eq, gt, width);
|
||||||
if (pivot == gt)
|
if (pivot == gt)
|
||||||
pivot = eq;
|
pivot = eq;
|
||||||
@@ -603,22 +633,30 @@ static qsort_pair qsort_partition(uint8_t* pbegin, uint8_t* pend, size_t width,
|
|||||||
return { lt, gt };
|
return { lt, gt };
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
static void qsort_impl(uint8_t* pbegin, uint8_t* pend, size_t width, int (*compar)(const void*, const void*, void*), void* arg)
|
||||||
static void qsort_impl(uint8_t* pbegin, uint8_t* pend, size_t width, int (*compar)(const void*, const void*, Args...), Args... args)
|
|
||||||
{
|
{
|
||||||
if (pbegin + width >= pend)
|
while (pbegin + width < pend)
|
||||||
|
{
|
||||||
|
if (pbegin + 16 * width <= pend)
|
||||||
|
{
|
||||||
|
for (uint8_t* ptr1 = pbegin; ptr1 < pend; ptr1 += width)
|
||||||
|
for (uint8_t* ptr2 = ptr1; ptr2 != pbegin && compar(ptr2 - width, ptr2, arg) > 0; ptr2 -= width)
|
||||||
|
qsort_swap(ptr2 - width, ptr2, width);
|
||||||
return;
|
return;
|
||||||
auto [lt, gt] = qsort_partition(pbegin, pend, width, compar, args...);
|
}
|
||||||
qsort_impl(pbegin, lt, width, compar, args...);
|
|
||||||
qsort_impl(gt, pend, width, compar, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
void qsort(void* base, size_t nel, size_t width, int (*compar)(const void*, const void*))
|
auto [lt, gt] = qsort_partition(pbegin, pend, width, compar, arg);
|
||||||
{
|
if (lt - pbegin < pend - gt)
|
||||||
if (width == 0 || nel <= 1)
|
{
|
||||||
return;
|
qsort_impl(pbegin, lt, width, compar, arg);
|
||||||
uint8_t* pbegin = static_cast<uint8_t*>(base);
|
pbegin = gt;
|
||||||
qsort_impl(pbegin, pbegin + nel * width, width, compar);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qsort_impl(gt, pend, width, compar, arg);
|
||||||
|
pend = lt;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void qsort_r(void* base, size_t nel, size_t width, int (*compar)(const void*, const void*, void*), void* arg)
|
void qsort_r(void* base, size_t nel, size_t width, int (*compar)(const void*, const void*, void*), void* arg)
|
||||||
@@ -629,6 +667,18 @@ void qsort_r(void* base, size_t nel, size_t width, int (*compar)(const void*, co
|
|||||||
qsort_impl(pbegin, pbegin + nel * width, width, compar, arg);
|
qsort_impl(pbegin, pbegin + nel * width, width, compar, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void qsort(void* base, size_t nel, size_t width, int (*compar)(const void*, const void*))
|
||||||
|
{
|
||||||
|
struct qsort_info_t {
|
||||||
|
int (*compar)(const void*, const void*);
|
||||||
|
};
|
||||||
|
constexpr auto qsort_compar = [](const void* a, const void* b, void* info) -> int {
|
||||||
|
return static_cast<qsort_info_t*>(info)->compar(a, b);
|
||||||
|
};
|
||||||
|
qsort_info_t info { compar };
|
||||||
|
return qsort_r(base, nel, width, qsort_compar, &info);
|
||||||
|
}
|
||||||
|
|
||||||
// Constants and algorithm from https://en.wikipedia.org/wiki/Permuted_congruential_generator
|
// Constants and algorithm from https://en.wikipedia.org/wiki/Permuted_congruential_generator
|
||||||
|
|
||||||
static constexpr uint64_t s_rand_multiplier = 6364136223846793005;
|
static constexpr uint64_t s_rand_multiplier = 6364136223846793005;
|
||||||
|
|||||||
Reference in New Issue
Block a user