Compare commits

...

2 Commits

4 changed files with 104 additions and 6 deletions

View File

@ -44,6 +44,8 @@ target_link_libraries(task1-randgen PRIVATE
add_executable(task1-sorter) add_executable(task1-sorter)
target_sources(task1-sorter PRIVATE target_sources(task1-sorter PRIVATE
src/task1-sorter.cpp) src/task1-sorter.cpp)
target_include_directories(task1-sorter PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(task1-sorter PRIVATE target_link_libraries(task1-sorter PRIVATE
Qt6::Core) Qt6::Core)

View File

@ -4,22 +4,30 @@
#include <mutex> #include <mutex>
#include <functional> #include <functional>
// General purpose mergesorter with multi threading support by Robin Dietzel <robin.dietzel@iem.thm.de>
template<typename T> template<typename T>
class MergeSorterMT { class MergeSorterMT {
public: public:
template<typename C> template<typename C>
MergeSorterMT(C cmp, int max_depth) : cmp(cmp), max_depth(max_depth) { 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<std::invoke_result_t<C, T, T>, bool>(), "C must be a function that returns a bool"); static_assert(std::is_same<std::invoke_result_t<C, T, T>, bool>(), "C must be a function that returns a bool");
} }
// Start sorting process
auto sort(std::vector<T> &data) -> void { auto sort(std::vector<T> &data) -> void {
// Create span: like a 'view' on the vector -> no unnecessary copies are made when subdividing sorting problem
std::span<T> sortable(data); std::span<T> sortable(data);
split(sortable, 0, max_depth); split(sortable, 0, max_depth);
} }
private: 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<T> &output, std::span<T> left, std::span<T> right) -> void { auto merge(std::span<T> &output, std::span<T> left, std::span<T> 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<T> buf; std::vector<T> buf;
buf.reserve(left.size() + right.size()); buf.reserve(left.size() + right.size());
@ -27,6 +35,7 @@ private:
auto r = right.begin(); auto r = right.begin();
auto o = buf.begin(); auto o = buf.begin();
// Insert from pre sorted half's
while (l < left.end() && r < right.end()) { while (l < left.end() && r < right.end()) {
if (cmp(*l, *r)) { if (cmp(*l, *r)) {
buf.insert(o, *l); buf.insert(o, *l);
@ -37,52 +46,72 @@ private:
} }
o++; o++;
} }
// Fill up with rest of left values
while (l < left.end()) { while (l < left.end()) {
buf.insert(o, *l); buf.insert(o, *l);
o++; o++;
l++; l++;
} }
// Fill up with rest of right values
while (r < right.end()) { while (r < right.end()) {
buf.insert(o, *r); buf.insert(o, *r);
o++; o++;
r++; 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()); std::move(buf.begin(), buf.end(), output.begin());
} }
// Splitup function
auto split(std::span<T> &data, int depth, const int &mdepth) -> void { auto split(std::span<T> &data, int depth, const int &mdepth) -> void {
if (std::distance(data.begin(), data.end()) <= 1) { if (std::distance(data.begin(), data.end()) <= 1) {
// Quit if only one element 'insortable'
return; return;
} else if (std::distance(data.begin(), data.end()) == 2) { } 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])) { if(cmp(data[1], data[0])) {
std::swap(data[0], data[1]); std::swap(data[0], data[1]);
return; return;
} }
} }
// Determine mid of data
auto mid = data.begin(); auto mid = data.begin();
std::advance(mid, std::distance(data.begin(), data.end()) / 2); std::advance(mid, std::distance(data.begin(), data.end()) / 2);
// Generate left and right view on data (no copies are made here)
std::span<T> left(data.begin(), mid); std::span<T> left(data.begin(), mid);
std::span<T> right(mid, data.end()); std::span<T> right(mid, data.end());
if (depth < mdepth) { if (depth < mdepth) {
// Create recursive split functions if maximum depth not reached
std::thread left_thread([&]() { split(left, depth + 1, mdepth); }); std::thread left_thread([&]() { split(left, depth + 1, mdepth); });
std::thread right_thread([&]() { split(right, 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(); left_thread.join();
right_thread.join(); right_thread.join();
} else { } else {
// Do normal recursion in a single thread if maximum depth is reached
split(left, depth + 1, mdepth); split(left, depth + 1, mdepth);
split(right, depth + 1, mdepth); split(right, depth + 1, mdepth);
} }
// Merge left and right together before returning
merge(data, left, right); merge(data, left, right);
return;
} }
private: private:
// Templated comparator function
std::function<bool(T, T)> cmp; std::function<bool(T, T)> cmp;
// Maximum depth
const int max_depth; const int max_depth;
}; };

View File

@ -8,7 +8,7 @@
#include <ranges> #include <ranges>
int main(int argc, char *argv[]) { auto main(int argc, char *argv[]) -> int{
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName("Random dataset generator"); QCoreApplication::setApplicationName("Random dataset generator");
QCoreApplication::setApplicationVersion("1.0"); QCoreApplication::setApplicationVersion("1.0");

View File

@ -7,8 +7,11 @@
#include <cmath> #include <cmath>
#include <thread> #include <thread>
#include <ranges> #include <ranges>
#include <chrono>
int main(int argc, char *argv[]) { #include <mergesort_mt.h>
auto main(int argc, char *argv[]) -> int {
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName("Multi purpose mergesort application"); QCoreApplication::setApplicationName("Multi purpose mergesort application");
QCoreApplication::setApplicationVersion("1.0.42"); QCoreApplication::setApplicationVersion("1.0.42");
@ -16,7 +19,8 @@ int main(int argc, char *argv[]) {
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription("Used to run either sequential or parallel mergesort on a texfile containing ascii encoded int32s"); parser.setApplicationDescription(
"Used to run either sequential or parallel mergesort on a texfile containing ascii encoded int32s");
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
@ -28,20 +32,51 @@ int main(int argc, char *argv[]) {
parser.addOption(sequential); parser.addOption(sequential);
parser.addOption(parallel); parser.addOption(parallel);
parser.addOption(nthreads); parser.addOption(nthreads);
parser.addOption(output);
parser.addPositionalArgument("dataset", "Filename where to load the data from"); parser.addPositionalArgument("dataset", "Filename where to load the data from");
parser.process(app); parser.process(app);
const QStringList args = parser.positionalArguments();
if (args.length() != 1) {
parser.showHelp(-1);
}
const QString source = args.at(0);
QFile input(source);
if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) {
print << "Could not open file " << source << " for reading" << Qt::endl;
print << input.errorString();
app.exit(-1);
return app.exec();
}
std::vector<int32_t> dataset;
QTextStream stream(&input);
while (!stream.atEnd()) {
QString line = stream.readLine();
bool ok;
int parsed_value = line.toUInt(&ok);
if (!ok) {
print << "Error converting value: " << line << Qt::endl;
} else {
dataset.push_back(std::move(parsed_value));
}
}
print << "Read " << dataset.size() << " values from " << source << Qt::endl;
const int threads = std::thread::hardware_concurrency(); const int threads = std::thread::hardware_concurrency();
int max_depth = std::sqrt(threads); int max_depth = std::sqrt(threads);
print << "Hardware concurrency of " << threads << " detected" << Qt::endl; print << "Hardware concurrency of " << threads << " detected" << Qt::endl;
if(parser.isSet(nthreads)) { if (parser.isSet(nthreads)) {
bool ok; bool ok;
max_depth = parser.value(nthreads).toInt(&ok); max_depth = parser.value(nthreads).toInt(&ok);
if(!ok) { if (!ok) {
parser.showHelp(-1); parser.showHelp(-1);
} }
print << "Overwriting maximum parallelized recursion depth with " << max_depth << Qt::endl; print << "Overwriting maximum parallelized recursion depth with " << max_depth << Qt::endl;
@ -49,7 +84,39 @@ int main(int argc, char *argv[]) {
print << "Assuming default parallelized recursion depth via sqrt(nthreads) of " << max_depth << Qt::endl; print << "Assuming default parallelized recursion depth via sqrt(nthreads) of " << max_depth << Qt::endl;
} }
if (parser.isSet(sequential)) {
auto buf = dataset;
auto t1 = std::chrono::high_resolution_clock::now();
MergeSorterMT<int32_t> sorter(
[](int32_t a, int32_t b) {
return (a > b);
}, 0);
sorter.sort(buf);
auto t2 = std::chrono::high_resolution_clock::now();
auto diff = t2 - t1;
print << "=> Duration for sequential sort: " << std::chrono::duration_cast<std::chrono::milliseconds>(diff).count() << " ms" << Qt::endl;
}
if (parser.isSet(parallel)) {
auto buf = dataset;
auto t1 = std::chrono::high_resolution_clock::now();
MergeSorterMT<int32_t> sorter(
[](int32_t a, int32_t b) {
return (a > b);
}, max_depth);
sorter.sort(buf);
auto t2 = std::chrono::high_resolution_clock::now();
auto diff = t2 - t1;
print << "=> Duration for parallel sort: " << std::chrono::duration_cast<std::chrono::milliseconds>(diff).count() << " ms" << Qt::endl;
}
if(parser.isSet(output)) {
print << "Sooory, not yet implemented :( you might do it yourself!" << Qt::endl;
app.exit(-1);
return app.exec();
}
app.exit(0); app.exit(0);
return 0; return 0;