#include #include #include #include #include // General purpose mergesorter with multi threading support by Robin Dietzel template class MergeSorterMT { public: template MergeSorterMT(C cmp, int max_depth) : cmp(cmp), max_depth(max_depth) { // Assert that cmp is a function that returns bool and takes two arguments of type T static_assert(std::is_same, bool>(), "C must be a function that returns a bool"); } // Start sorting process auto sort(std::vector &data) -> void { // Create span: like a 'view' on the vector -> no unnecessary copies are made when subdividing sorting problem std::span sortable(data); split(sortable, 0, max_depth); } private: // Merge function that merges left & right span into the output span // No exclusive access on output is necessary (e.g. via mutex) because all parallel threads work on different parts of output auto merge(std::span &output, std::span left, std::span right) -> void { // Create buffer, here we need a temporary container where we copy values to, because left and right are a view on parts // of output std::vector buf; buf.reserve(left.size() + right.size()); auto l = left.begin(); auto r = right.begin(); auto o = buf.begin(); // Insert from pre sorted half's while (l < left.end() && r < right.end()) { if (cmp(*l, *r)) { buf.insert(o, *l); l++; } else { buf.insert(o, *r); r++; } o++; } // Fill up with rest of left values while (l < left.end()) { buf.insert(o, *l); o++; l++; } // Fill up with rest of right values while (r < right.end()) { buf.insert(o, *r); o++; r++; } // Completely move buffer to output // IMPORTANT: left and right are still a view on the splitted output, that is now sorted std::move(buf.begin(), buf.end(), output.begin()); } // Splitup function auto split(std::span &data, int depth, const int &mdepth) -> void { if (std::distance(data.begin(), data.end()) <= 1) { // Quit if only one element 'insortable' return; } else if (std::distance(data.begin(), data.end()) == 2) { // Swap two values dependant on size for small speedup (no call to further split must be made) if(cmp(data[1], data[0])) { std::swap(data[0], data[1]); return; } } // Determine mid of data auto mid = data.begin(); std::advance(mid, std::distance(data.begin(), data.end()) / 2); // Generate left and right view on data (no copies are made here) std::span left(data.begin(), mid); std::span right(mid, data.end()); if (depth < mdepth) { // Create recursive split functions if maximum depth not reached std::thread left_thread([&]() { split(left, depth + 1, mdepth); }); std::thread right_thread([&]() { split(right, depth + 1, mdepth); }); // Both threads must join before we could further work on the data viewed // by left and right (recursively sorted by the both calls) left_thread.join(); right_thread.join(); } else { // Do normal recursion in a single thread if maximum depth is reached split(left, depth + 1, mdepth); split(right, depth + 1, mdepth); } // Merge left and right together before returning merge(data, left, right); return; } private: // Templated comparator function std::function cmp; // Maximum depth const int max_depth; };