2025-07-28 22:25:26 +02:00
|
|
|
#include "sorter.hpp"
|
|
|
|
|
|
2025-09-26 14:33:16 +02:00
|
|
|
#include "single_task_handler.hpp"
|
|
|
|
|
#include "thread_pool.hpp"
|
|
|
|
|
|
2025-07-28 22:25:26 +02:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <iterator>
|
2025-07-29 21:45:53 +02:00
|
|
|
#include <cassert>
|
|
|
|
|
#include <iostream>
|
2025-07-30 15:05:52 +02:00
|
|
|
#include <bitset>
|
|
|
|
|
#include <climits>
|
2025-07-28 22:25:26 +02:00
|
|
|
|
2025-07-30 22:07:29 +02:00
|
|
|
#define DEBUG false
|
|
|
|
|
|
2025-07-28 22:25:26 +02:00
|
|
|
namespace ae {
|
|
|
|
|
|
2025-09-26 13:17:02 +02:00
|
|
|
sorter::sorter(uint32_t num = 1) {
|
|
|
|
|
sorter::num_threads = num;
|
2025-09-26 14:33:16 +02:00
|
|
|
if (num > 1) {
|
|
|
|
|
sorter::handler = (TaskHandler*) new ThreadPool(num);
|
|
|
|
|
} else {
|
|
|
|
|
sorter::handler = (TaskHandler*) new SingleTaskHandler();
|
|
|
|
|
}
|
2025-09-26 13:17:02 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-28 22:25:26 +02:00
|
|
|
void sorter::sort(container& data) {
|
|
|
|
|
for (auto i = 1uz; i < data.placeholder_.size(); ++i) {
|
|
|
|
|
std::ranges::copy(data.placeholder_[i], std::back_inserter(data.placeholder_[0]));
|
|
|
|
|
data.placeholder_[i].clear();
|
|
|
|
|
}
|
2025-07-30 22:07:29 +02:00
|
|
|
#if DEBUG
|
2025-09-25 20:32:42 +02:00
|
|
|
for (int i = 0; i < data.placeholder_[0].size(); i++) {
|
2025-09-26 13:17:02 +02:00
|
|
|
if (copy[i] != data.placeholder_[0][i])
|
2025-09-25 20:32:42 +02:00
|
|
|
std::cerr << i << " before:" << data.placeholder_[0][i] << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 22:07:29 +02:00
|
|
|
std::vector<container::element_type> copy;
|
|
|
|
|
std::ranges::copy(data.placeholder_[0], std::back_inserter(copy));
|
|
|
|
|
std::sort(copy.begin(), copy.end());
|
|
|
|
|
#endif
|
2025-07-29 21:45:53 +02:00
|
|
|
sorter::msd_inplace_radix_sort(data.placeholder_[0], 0, [&](auto span) {sorter::robin_hood_sort(span);});
|
2025-09-26 14:33:16 +02:00
|
|
|
while (sorter::handler->size() > 0 || sorter::handler->isWorking()) {};
|
2025-07-30 22:07:29 +02:00
|
|
|
#if DEBUG
|
|
|
|
|
for (int i = 0; i < copy.size(); i++) {
|
|
|
|
|
if (copy[i] != data.placeholder_[0][i])
|
|
|
|
|
std::cerr << i << " " << "sorted: " << copy[i] << " actual:" << data.placeholder_[0][i] << std::endl;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2025-07-28 22:25:26 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-25 20:32:42 +02:00
|
|
|
void sorter::msd_inplace_radix_sort_binary(
|
2025-07-29 21:45:53 +02:00
|
|
|
std::span<container::element_type> range,
|
|
|
|
|
size_t passes,
|
|
|
|
|
const std::function<void(std::span<container::element_type> bucket)>& bucket_sort
|
|
|
|
|
) {
|
2025-07-30 22:07:29 +02:00
|
|
|
if (std::begin(range) >= std::end(range)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-07-29 21:45:53 +02:00
|
|
|
if (sorter::RADIX_ITERATIONS == passes) {
|
2025-07-30 22:07:29 +02:00
|
|
|
switch (range.size()) {
|
|
|
|
|
case 1: return;
|
|
|
|
|
case 2:
|
|
|
|
|
if (range[0] >= range[1]) {
|
|
|
|
|
std::swap(range[0], range[1]);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
default:
|
|
|
|
|
bucket_sort(range);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (range.size() > 1) {
|
2025-07-30 15:05:52 +02:00
|
|
|
bucket_sort(range);
|
|
|
|
|
}
|
2025-07-28 22:25:26 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto lower = std::begin(range);
|
|
|
|
|
auto upper = std::end(range);
|
2025-07-30 22:07:29 +02:00
|
|
|
while (lower < upper) {
|
|
|
|
|
|
|
|
|
|
if (*lower & (1L << (sizeof(container::element_type) * CHAR_BIT - passes - 1))) {
|
|
|
|
|
// The <passes>-left bit is set, so move to the beginning of the end section and decrement the upper iterator
|
2025-07-29 21:45:53 +02:00
|
|
|
--upper;
|
2025-07-30 22:07:29 +02:00
|
|
|
std::swap(*upper, *lower);
|
2025-07-29 21:45:53 +02:00
|
|
|
} else {
|
|
|
|
|
++lower;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-30 22:07:29 +02:00
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "pass: " << passes << " begin: " << &*std::begin(range) << " end: " << &*std::end(range) << " lower: " << &*lower << std::endl;
|
|
|
|
|
#endif
|
2025-09-25 20:32:42 +02:00
|
|
|
sorter::msd_inplace_radix_sort_binary(std::span<container::element_type> (std::begin(range), lower), passes + 1, bucket_sort);
|
|
|
|
|
sorter::msd_inplace_radix_sort_binary(std::span<container::element_type> (lower, std::end(range)), passes + 1, bucket_sort);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sorter::msd_inplace_radix_sort(
|
|
|
|
|
std::span<container::element_type> range,
|
|
|
|
|
size_t passes,
|
|
|
|
|
const std::function<void(std::span<container::element_type> bucket)>& bucket_sort
|
|
|
|
|
) {
|
|
|
|
|
if (std::begin(range) >= std::end(range)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (range.size() <= sorter::SMALL_SORT_THRESHHOLD) {
|
|
|
|
|
bucket_sort(range);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We first determine the number of elements per bucket
|
|
|
|
|
// This is one pass additional pass over the elements and needs O(buckets) additional space, so in one configuration constant overhead
|
|
|
|
|
uint32_t bucket_sizes[sorter::RADIX_BUCKETS] = { 0 };
|
|
|
|
|
auto upper_bucket_mask = ((1L << sorter::RADIX_SIZE) - 1) << (sizeof(container::element_type) * CHAR_BIT - sorter::RADIX_SIZE * (1 + passes));
|
|
|
|
|
|
|
|
|
|
auto mask_bucket = [&](container::element_type* element){ return (*element & upper_bucket_mask) >> (sizeof(container::element_type) * CHAR_BIT - sorter::RADIX_SIZE) * (1 + passes); };
|
|
|
|
|
|
|
|
|
|
for (auto element : range) {
|
|
|
|
|
auto bucket = mask_bucket(&element);
|
|
|
|
|
bucket_sizes[bucket]++;
|
|
|
|
|
}
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Bucket sizes: ";
|
|
|
|
|
for (auto bucket : bucket_sizes) {
|
|
|
|
|
std::cerr << bucket << " ";
|
|
|
|
|
}
|
|
|
|
|
std::cerr << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// We now point each bucket to its start location in the range
|
|
|
|
|
container::element_type* buckets_end[sorter::RADIX_BUCKETS];
|
|
|
|
|
container::element_type* buckets_start[sorter::RADIX_BUCKETS];
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Starting bucket" << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
auto count = 0;
|
|
|
|
|
for (int i = 0; i < sorter::RADIX_BUCKETS; ++i) {
|
|
|
|
|
buckets_end[i] = &range[count];
|
|
|
|
|
buckets_start[i] = &range[count];
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "bucket " << i << " at " << count << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
count += bucket_sizes[i];
|
|
|
|
|
}
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "finish" << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
// Loop over the elements and swap them into the correct buckets.
|
|
|
|
|
// This will look at each element exactly once.
|
|
|
|
|
auto element = &range[0];
|
|
|
|
|
while (element < &*std::end(range)) {
|
|
|
|
|
uint32_t bucket = mask_bucket(element);
|
|
|
|
|
|
|
|
|
|
// Check if we are currently in the bounds of the corresponding bucket
|
|
|
|
|
if (&*element >= buckets_start[bucket] && &*element < buckets_end[bucket]) {
|
|
|
|
|
// The element is in the correct bucket, we skip to the end of the bucket
|
|
|
|
|
element = buckets_end[bucket];
|
|
|
|
|
} else {
|
|
|
|
|
// The element is not in the correct bucket; swap
|
|
|
|
|
std::swap(*element, *buckets_end[bucket]);
|
|
|
|
|
buckets_end[bucket]++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#if DEBUG
|
|
|
|
|
for (int i = 0; i < range.size(); i++) {
|
|
|
|
|
std::cerr << i << " reordered:" << range[i] << std::endl;
|
|
|
|
|
}
|
|
|
|
|
std::cerr << "Finish reordering elements" << std::endl;
|
|
|
|
|
std::cerr << "Bucket elements at begin of bucket" << std::endl;
|
|
|
|
|
for (auto bucket : buckets_start) {
|
|
|
|
|
std::cerr << *bucket << " bucket " << mask_bucket(bucket) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
std::cerr << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
for (auto i = 0; i < sorter::RADIX_BUCKETS - 1; ++i) {
|
|
|
|
|
assert(buckets_end[i] == buckets_start[i + 1]);
|
|
|
|
|
}
|
|
|
|
|
assert(buckets_end[sorter::RADIX_BUCKETS - 1] == &*std::end(range));
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Ranges of buckets are correct" << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// sort each bucket recursively
|
|
|
|
|
for (auto i = 0; i < sorter::RADIX_BUCKETS; i++) {
|
2025-09-26 14:33:16 +02:00
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Putting in task with depth " << passes << " of bucket " << i << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
auto start = buckets_start[i];
|
|
|
|
|
auto end = buckets_end[i];
|
|
|
|
|
sorter::handler->add([start, end, &bucket_sort, passes, this, i](){
|
2025-09-26 13:17:02 +02:00
|
|
|
#if DEBUG
|
2025-09-26 14:33:16 +02:00
|
|
|
std::cerr << "Starting task with depth " << passes << " of bucket " << i << std::endl;
|
2025-09-26 13:17:02 +02:00
|
|
|
#endif
|
2025-09-26 14:33:16 +02:00
|
|
|
sorter::msd_inplace_radix_sort(std::span<container::element_type> (start, end), passes + 1, bucket_sort);
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Finishing task with depth " << passes << " of bucket " << i << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
});
|
2025-09-25 20:32:42 +02:00
|
|
|
}
|
2025-07-29 21:45:53 +02:00
|
|
|
}
|
2025-09-26 13:17:02 +02:00
|
|
|
|
2025-07-29 21:45:53 +02:00
|
|
|
void sorter::robin_hood_sort(std::span<container::element_type> bucket) {
|
|
|
|
|
const auto size = bucket.size() + sorter::OVERHEAD_SIZE;
|
2025-07-30 15:05:52 +02:00
|
|
|
const auto mask = ((1L) << (sizeof(container::element_type) * CHAR_BIT - sorter::RADIX_ITERATIONS)) - 1;
|
2025-07-30 22:07:29 +02:00
|
|
|
std::vector<container::element_type> space(size, -1L);
|
2025-07-29 21:45:53 +02:00
|
|
|
for (auto element : bucket) {
|
|
|
|
|
auto masked_element = (element & mask);
|
2025-07-30 22:07:29 +02:00
|
|
|
auto index = ((masked_element) * bucket.size()) / mask;
|
2025-07-29 21:45:53 +02:00
|
|
|
if (space[index] == -1) {
|
2025-07-30 22:07:29 +02:00
|
|
|
space[index] = element;
|
2025-07-28 22:25:26 +02:00
|
|
|
} else {
|
2025-07-30 22:07:29 +02:00
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Linear probing of " << element << " at index " << index << ". Current element " << space[index] << std::endl;
|
|
|
|
|
#endif
|
2025-07-29 21:45:53 +02:00
|
|
|
auto i = index;
|
2025-07-30 22:07:29 +02:00
|
|
|
// linear probing
|
|
|
|
|
while (i < size - 1 && space[i] != -1) {++i;};
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Inserting " << element << " at index " << i << " instead of " << index << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
space[i] = element;
|
2025-07-29 21:45:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-30 22:07:29 +02:00
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Unsorted\n";
|
|
|
|
|
for (auto element : space) {
|
|
|
|
|
std::cerr << element << " ";
|
|
|
|
|
}
|
|
|
|
|
std::cerr << std::endl;
|
|
|
|
|
#endif
|
2025-07-28 22:25:26 +02:00
|
|
|
|
2025-07-29 21:45:53 +02:00
|
|
|
// One final pass to correct linear probing errors
|
|
|
|
|
for (auto i = 1; i < size; ++i) {
|
|
|
|
|
auto j = i;
|
2025-07-30 22:07:29 +02:00
|
|
|
while ((uint64_t) space[j-1] > space[j] && j > 0) {
|
2025-07-29 21:45:53 +02:00
|
|
|
std::swap((space[j]),space[j-1]);
|
2025-07-30 22:07:29 +02:00
|
|
|
j--;
|
2025-07-28 22:25:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-29 21:45:53 +02:00
|
|
|
|
2025-07-30 22:07:29 +02:00
|
|
|
#if DEBUG
|
|
|
|
|
std::cerr << "Original\n";
|
|
|
|
|
for (auto element : bucket) {
|
|
|
|
|
std::cerr << element << " ";
|
|
|
|
|
}
|
|
|
|
|
std::cerr << std::endl;
|
|
|
|
|
|
|
|
|
|
std::cerr << "Checking if sorted\n";
|
|
|
|
|
for (auto element : space) {
|
|
|
|
|
std::cerr << element << " ";
|
|
|
|
|
}
|
|
|
|
|
std::cerr << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-07-29 21:45:53 +02:00
|
|
|
// copy data back into original range
|
|
|
|
|
auto i = 0;
|
|
|
|
|
for (auto element = std::begin(bucket); element < std::end(bucket); ++element) {
|
|
|
|
|
*element = space[i];
|
2025-07-30 22:07:29 +02:00
|
|
|
++i;
|
2025-07-29 21:45:53 +02:00
|
|
|
}
|
2025-07-28 22:25:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace ae
|