diff --git a/CMakeLists.txt b/CMakeLists.txt index afc6e34..536554d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/task2/CMakeLists.txt b/task2/CMakeLists.txt new file mode 100644 index 0000000..8badcc8 --- /dev/null +++ b/task2/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/task2/include/quicksort_mt.h b/task2/include/quicksort_mt.h new file mode 100644 index 0000000..6d57e78 --- /dev/null +++ b/task2/include/quicksort_mt.h @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include + +template +class QuickSorterMT { +public: + template + QuickSorterMT(C cmp, int max_depth) : cmp(cmp), max_depth(max_depth) { + static_assert(std::is_same, bool>(), "C must be a function that returns a bool"); + } + + auto sort(std::vector &data) -> void { + std::span sortable(data); + qsort(sortable, 0, max_depth); + } + +private: + auto parition(std::span &data) -> std::pair, std::span> { + + std::vector 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(data.begin(), lefti), std::span(lefti, data.end())}; + } + + + auto qsort(std::span &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 left(data.begin(), mid); + std::span 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 cmp; + const int max_depth; +}; \ No newline at end of file diff --git a/task2/quicksort_mt.h b/task2/quicksort_mt.h deleted file mode 100644 index 07d4684..0000000 --- a/task2/quicksort_mt.h +++ /dev/null @@ -1,3 +0,0 @@ -#include -#include - diff --git a/task2/src/task2-auto.cpp b/task2/src/task2-auto.cpp new file mode 100644 index 0000000..b20a6f0 --- /dev/null +++ b/task2/src/task2-auto.cpp @@ -0,0 +1,114 @@ +#include "fmt/format.h" +#include +#include +#include +#include +#include + +#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 Amdahl’s 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 sort’s 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 +auto parse_file(std::ifstream &stream, std::vector &vec) -> void { + std::string buf; + T convbuf; + + while (std::getline(stream, buf)) { + convbuf = static_cast(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 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 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(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 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(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(t_seq.count())); + fmt::print("\nt_par = {: > 5.2f} ms", static_cast(t_par.count())); + fmt::print("\nspeedup = {: > 5.2f}", (1.0 * t_seq / t_par)); + fmt::print("\nDelta_t = {: > 5.2f} ms", static_cast(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; + } + +} diff --git a/third-party/fmt b/third-party/fmt index 045b05d..5d55375 160000 --- a/third-party/fmt +++ b/third-party/fmt @@ -1 +1 @@ -Subproject commit 045b05d79e8c827ea815d765e62d92b879184b41 +Subproject commit 5d55375a8a6aabf39528bdf48f7b3ded5ef4e9bb