Implement quicksort in parallel

This commit is contained in:
Robin Dietzel 2023-12-05 10:49:07 +01:00
parent fa11a8d942
commit f7d12edd60
6 changed files with 209 additions and 4 deletions

View File

@ -26,5 +26,6 @@ add_subdirectory(third-party/fmt)
# Include CMakeLists files from subdirs for specific tasks
add_subdirectory(task1)
add_subdirectory(task2)
add_subdirectory(task3)
add_subdirectory(task4)

14
task2/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
find_package(Qt6 COMPONENTS Core REQUIRED)
find_package(fmt)
add_executable(task2-auto)
add_dependencies(task2-auto task1-dataset)
target_sources(task2-auto PRIVATE
src/task2-auto.cpp)
target_include_directories(task2-auto PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(task2-auto PRIVATE
fmt::fmt)
install(TARGETS task2-auto DESTINATION bin)
install(IMPORTED_RUNTIME_ARTIFACTS task2-auto DESTINATION lib)

View File

@ -0,0 +1,79 @@
#include <vector>
#include <span>
#include <thread>
#include <mutex>
#include <functional>
template<typename T>
class QuickSorterMT {
public:
template<typename C>
QuickSorterMT(C cmp, int max_depth) : cmp(cmp), max_depth(max_depth) {
static_assert(std::is_same<std::invoke_result_t<C, T, T>, bool>(), "C must be a function that returns a bool");
}
auto sort(std::vector<T> &data) -> void {
std::span<T> sortable(data);
qsort(sortable, 0, max_depth);
}
private:
auto parition(std::span<T> &data) -> std::pair<std::span<T>, std::span<T>> {
std::vector<T> buf(data);
auto pivot = buf.begin();
std::advance(pivot, std::distance(buf.begin(), buf.end()) / 2);
auto lefti = buf.begin();
auto righti = buf.end();
while (1) {
for (; cmp(*lefti, *pivot); lefti++);
for (; !cmp(*righti, *pivot); righti--);
if (lefti >= righti) {
break;
}
std::swap(lefti, righti);
}
std::move(buf.begin(), buf.end(), data.begin());
return {std::span<T>(data.begin(), lefti), std::span<T>(lefti, data.end())};
}
auto qsort(std::span<T> &data, int depth, const int &mdepth) -> void {
if (std::distance(data.begin(), data.end()) <= 1) {
return;
} else if (std::distance(data.begin(), data.end()) == 2) {
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<T> left(data.begin(), mid);
std::span<T> right(mid, data.end());
if (depth < mdepth) {
std::thread left_thread([&]() { qsort(left, depth + 1, mdepth); });
std::thread right_thread([&]() { qsort(right, depth + 1, mdepth); });
left_thread.join();
right_thread.join();
} else {
qsort(left, depth + 1, mdepth);
qsort(right, depth + 1, mdepth);
}
}
std::function<bool(T, T)> cmp;
const int max_depth;
};

View File

@ -1,3 +0,0 @@
#include <vector>
#include <span>

114
task2/src/task2-auto.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "fmt/format.h"
#include <vector>
#include <fstream>
#include <string>
#include <chrono>
#include <cmath>
#include "quicksort_mt.h"
/*
Create a simple sorting application that uses the mergesort algorithm to sort a
large collection (e.g., 10^7 ) of 32-bit integers. The input data and output results
should be stored in files, and the I/O operations should be considered a
sequential part of the application. Mergesort is an algorithm that is considered
appropriate for parallel execution, although it cannot be equally divided between
an arbitrary number of processors, as Amdahls and Gustafson-Barsis laws
require.
Assuming that this equal division is possible, estimate α, i.e., the part of the
program that can be parallelized, by using a profiler like gprof or valgrind to
measure the duration of sorts execution relative to the overall execution
time. Use this number to estimate the predicted speedup for your program.
Does α depend on the size of the input? If it does, how should you modify
your predictions and their graphical illustration?
*/
template<typename T>
auto parse_file(std::ifstream &stream, std::vector<T> &vec) -> void {
std::string buf;
T convbuf;
while (std::getline(stream, buf)) {
convbuf = static_cast<T>(std::stoul(buf));
vec.emplace_back(std::move(convbuf));
}
}
auto main(int argc, char *argv[]) -> int {
try {
const auto path = "../task1/dataset.dat";
std::ifstream file(path, std::ios_base::in);
if (!file.is_open()) {
fmt::print("\nError opening file");
return -1;
}
fmt::print("\nOpened file {} sucessfully", path);
std::vector<int32_t> dataset;
parse_file(file, dataset);
fmt::print("\nRead {} values from {}", dataset.size(), path);
auto dataset_par = dataset;
auto dataset_seq = dataset;
auto t1 = std::chrono::high_resolution_clock::now();
QuickSorterMT<int32_t> msst([](int32_t a, int32_t b) {
return (a > b);
}, 0);
msst.sort(dataset_seq);
auto t2 = std::chrono::high_resolution_clock::now();
auto t_seq = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
fmt::print("\nSorted {} entries within {} ms in sequential", dataset_seq.size(), t_seq.count());
const int threads = std::thread::hardware_concurrency();
const int max_depth = std::sqrt(threads);
t1 = std::chrono::high_resolution_clock::now();
QuickSorterMT<int32_t> msmt([](int32_t a, int32_t b) {
return (a > b);
}, max_depth);
msmt.sort(dataset_par);
t2 = std::chrono::high_resolution_clock::now();
auto t_par = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
fmt::print("\nSorted {} entries within {} ms in parallel on a system having {} threads and a recursion depth of {}"
"\nresulting in a total count of {} threads",
dataset_seq.size(), t_par.count(), threads, max_depth, std::pow(2, max_depth));
auto eq = (dataset_seq == dataset_par);
fmt::print("\nCheck whether sorted arrays are equal: {}", (eq) ? "Equal" : "not equal");
fmt::print("\n\n------------Summary------------");
fmt::print("\nt_seq = {: > 5.2f} ms", static_cast<float>(t_seq.count()));
fmt::print("\nt_par = {: > 5.2f} ms", static_cast<float>(t_par.count()));
fmt::print("\nspeedup = {: > 5.2f}", (1.0 * t_seq / t_par));
fmt::print("\nDelta_t = {: > 5.2f} ms", static_cast<float>(t_seq.count() - t_par.count()));
fmt::print("\n-------------------------------");
std::ofstream ofile("dataset.out.dat", std::ios_base::out);
if (!ofile.is_open()) {
fmt::print("\nError writing to file");
return -1;
}
for (auto &element: dataset_seq) {
ofile << std::to_string(element) << '\n';
}
file.close();
ofile.flush();
ofile.close();
fmt::print("\nWritten to output file");
return 0;
} catch (std::exception &e) {
fmt::print("\nError occured: {}", e.what());
return -1;
}
}

2
third-party/fmt vendored

@ -1 +1 @@
Subproject commit 045b05d79e8c827ea815d765e62d92b879184b41
Subproject commit 5d55375a8a6aabf39528bdf48f7b3ded5ef4e9bb