diff --git a/.gitignore b/.gitignore index b849c63..2114a24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -cmake-build-* +.out .idea task1/dataset.dat \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ee5a92b..40b601c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,28 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.25) project(aca-tasks) +# Set C++ standard set(CMAKE_CXX_STANDARD 20) -set(CMAKE_INCLUDE_CURRENT_DIR ON) +# Set install directory set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/installed) +# Set compiler flags for optimization on Release build if (${CMAKE_BUILD_TYPE} STREQUAL "Release") + # Compiler options add_compile_options( -Wall -Wpedantic -O3 - -g3 - ) + -g3) + + # Defines for some libraries add_compile_definitions( NDEBUG) endif () +# Include general purpose libraries to build add_subdirectory(third-party/fmt) + +# Include CMakeLists files from subdirs for specific tasks add_subdirectory(task1) \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..7a6500e --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,20 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "configurePresets": [ + { + "name": "task1@release", + "displayName": "Task1 Release build", + "description": "Builds the targets of task1 as release", + "generator": "Ninja", + "binaryDir": "${sourceDir}/.out/task1-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ] +} \ No newline at end of file diff --git a/task1/CMakeLists.txt b/task1/CMakeLists.txt index e1658c9..9add3cd 100644 --- a/task1/CMakeLists.txt +++ b/task1/CMakeLists.txt @@ -1,29 +1,38 @@ -find_package(Python3 COMPONENTS Interpreter REQUIRED) +# Find packages necessary for this application +find_package(Qt6 COMPONENTS Core REQUIRED) find_package(fmt) +# Search for python to generate the test dataset +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +# Generate random dataset add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/dataset.dat DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/dataset-gen.py COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/dataset-gen.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Generating random dataset") +# Copy random dataset to binary dir add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dataset.dat DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/dataset.dat COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/dataset.dat ${CMAKE_CURRENT_BINARY_DIR}/dataset.dat COMMENT "Copying dataset") -add_custom_target(task1_7_dataset +add_custom_target(task1-dataset DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/dataset.dat ${CMAKE_CURRENT_BINARY_DIR}/dataset.dat) -add_executable(task1_7 main.cpp - mergesort_mt.h) -target_link_libraries(task1_7 PRIVATE -fmt::fmt) +# Add task1 automated target (Automatically loads generated dataset +add_executable(task1-auto) +add_dependencies(task1-auto task1-dataset) +target_sources(task1-auto PRIVATE + task1-auto.cpp) +target_include_directories(task1-auto PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(task1-auto PRIVATE + fmt::fmt) -install(TARGETS task1_7 DESTINATION bin) -install(IMPORTED_RUNTIME_ARTIFACTS task1_7 DESTINATION bin) - -add_dependencies(task1_7 task1_7_dataset) +install(TARGETS task1-auto DESTINATION bin) +install(IMPORTED_RUNTIME_ARTIFACTS task1-auto DESTINATION lib) \ No newline at end of file diff --git a/task1/mergesort_mt.h b/task1/include/mergesort_mt.h similarity index 100% rename from task1/mergesort_mt.h rename to task1/include/mergesort_mt.h diff --git a/task1/main2.cpp b/task1/main2.cpp new file mode 100644 index 0000000..d958416 --- /dev/null +++ b/task1/main2.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +#include "include/mergesort_mt.h" + +//Random generator by Prof. Weber +auto gen_file_input(const std::string &fname) -> void { + FILE* fp = fopen(fname.c_str(), "w"); + int amount = 1e2; + int* randNum = (int *)malloc(amount * sizeof(int)); + for(int i = 0; i < amount; i++){ + *(randNum + i) = QRandomGenerator::global()->generate(); + fprintf(fp, "%d\n", *(randNum + i)); + } +} + +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)); + } +} + +int main(int argc, char *argv[]) +{ + const std::string fname = "b1-7_input.data"; + //Generate file first using Qt random generator + gen_file_input(fname); + try { + std::ifstream file(fname.c_str(), std::ios_base::in); + if (!file.is_open()) { + fmt::print("\nError opening file"); + return -1; + } + + fmt::print("\nOpened file {} sucessfully", fname); + std::vector dataset; + + parse_file(file, dataset); + fmt::print("\nRead {} values from {}", dataset.size(), fname); + + auto dataset_par = dataset; + auto dataset_seq = dataset; + + auto t1 = std::chrono::high_resolution_clock::now(); + MergeSorterMT 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(); + MergeSorterMT 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; + } +} \ No newline at end of file diff --git a/task1/mergesort.h b/task1/mergesort.h deleted file mode 100644 index b5ad3eb..0000000 --- a/task1/mergesort.h +++ /dev/null @@ -1,379 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace algo { - - class MergeSort_v1 { - private: - template - static auto - merge(Iterator start, Iterator middle, Iterator end, Comparator cmp, Iterator output_start) -> void { - Iterator start_m = start; - Iterator begin = output_start; - Iterator start2 = middle + 1; - - //merge from input until one half completes - while (start <= middle && start2 <= end) { - if (cmp(*start, *start2)) { - *output_start = *start; - start++; - } else { - *output_start = *start2; - start2++; - } - output_start++; - } - - //try to finish first half - while (start <= middle) { - *output_start = *start; - start++; - output_start++; - } - - while (start2 <= end) { - *output_start = *start2; - start2++; - output_start++; - } - - const auto size = std::distance(start_m, end); - for (auto i = 0; i <= size; i++, start_m++, begin++) { - *start_m = *begin; - } - - } - - template - static auto - ms_split(Container &output_vec, Iterator start, Iterator end, Comparator cmp, Iterator output_start) -> void { - Iterator mid = start; - Iterator begin = output_start; - - if (std::distance(start, end) < 1) { - return; - } else { - //move mid iterator litterally to the mid - std::advance(mid, std::distance(start, end) / 2); - //sort the first half within an recursion - ms_split(output_vec, start, mid, cmp, output_start); - - //move output iterator - std::advance(output_start, std::distance(start, mid + 1)); - //sort the second half within a recursion - ms_split(output_vec, mid + 1, end, cmp, output_start); - - //merge everything together starting from the complete beginning - merge(start, mid, end, cmp, begin); - } - } - - public: - template - static auto sort(Iterator start, Iterator end, Comparator cmp) -> void { - using valtype = typename std::iterator_traits::value_type; - std::vector temporary_dataset(std::distance(start, end)); - ms_split(temporary_dataset, start, end - 1, cmp, temporary_dataset.begin()); - } - }; - - class MergeSort_v2 { - private: - - template - static auto - mt_merge(Container left, Container right, Comparator cmp) -> Container { - //using Iterator = typename std::iterator_traits::value_type; - - Container output; - - auto lefti = left.begin(); - auto righti = right.begin(); - - while (lefti < left.end() && righti < right.end()) { - if (cmp(*lefti, *righti)) { - output.emplace_back(std::move(*lefti)); - lefti++; - } else { - output.emplace_back(std::move(*righti)); - righti++; - } - } - - while (lefti < left.end()) { - output.emplace_back(std::move(*lefti)); - lefti++; - } - while (righti < right.end()) { - output.emplace_back(std::move(*righti)); - righti++; - } - - return output; - } - - template - static auto - merge(Iterator start, Iterator middle, Iterator end, Comparator cmp, Iterator output_start, - std::recursive_mutex &dataset_guard) -> void { - Iterator start_m = start; - Iterator begin = output_start; - Iterator start2 = middle + 1; - - //merge from input until one half completes - while (start <= middle && start2 <= end) { - if (cmp(*start, *start2)) { - *output_start = *start; - start++; - } else { - *output_start = *start2; - start2++; - } - output_start++; - } - - //try to finish first half - while (start <= middle) { - *output_start = *start; - start++; - output_start++; - } - - while (start2 <= end) { - *output_start = *start2; - start2++; - output_start++; - } - - dataset_guard.lock(); - const auto size = std::distance(start_m, end); - for (auto i = 0; i <= size; i++, start_m++, begin++) { - *start_m = *begin; - } - dataset_guard.unlock(); - - } - - template - static auto mt_split(Container &output_vec, Iterator start, Iterator end, Comparator cmp, Iterator output_start, - int &nthreads, std::recursive_mutex &dataset_guard, std::mutex &depth_guard) -> void { - Iterator mid = start; - Iterator to_start = output_start; - - if (std::distance(start, end) < 1) { - return; - - } - - bool rem_threads; - { - std::lock_guard guard(depth_guard); //RAII guard - rem_threads = nthreads > 1; - } - - if (rem_threads) { - { - std::lock_guard guard(depth_guard); //RAII guard - nthreads -= 2; - } - - std::advance(mid, std::distance(start, end) / 2); - std::thread t1([&]() { - mt_split(output_vec, start, mid, cmp, output_start, nthreads, dataset_guard, depth_guard); - }); - - std::advance(output_start, std::distance(start, mid + 1)); - std::thread t2([&]() { - mt_split(output_vec, mid + 1, end, cmp, output_start, nthreads, dataset_guard, depth_guard); - }); - - - //merge everything together starting from the complete beginning - t1.join(); - t2.join(); - std::vector left; - left.assign(start, mid); - std::vector right; - right.assign(mid + 1, end); - - mt_merge(left, right, cmp); - } else { - //move mid iterator litterally to the mid - std::advance(mid, std::distance(start, end) / 2); - //sort the first half within an recursion - mt_split(output_vec, start, mid, cmp, output_start, nthreads, dataset_guard, depth_guard); - - //move output iterator - std::advance(output_start, std::distance(start, mid + 1)); - //sort the second half within a recursion - mt_split(output_vec, mid + 1, end, cmp, output_start, nthreads, dataset_guard, depth_guard); - - std::vector left; - left.assign(start, mid); - std::vector right; - right.assign(mid + 1, end); - - mt_merge(left, right, cmp); - } - } - - template - static auto ms_split(Container &output_vec, Iterator start, Iterator end, Comparator cmp, Iterator output_start, - int &nthreads, std::recursive_mutex &dataset_guard, std::mutex &depth_guard) -> void { - Iterator mid = start; - Iterator begin = output_start; - - if (std::distance(start, end) < 1) { - //Quit on smalles list size (one element is always sorted) - return; - } else { - if (nthreads > 1) { - depth_guard.lock(); - nthreads -= 2; - depth_guard.unlock(); - //move mid iterator litterally to the mid - std::advance(mid, std::distance(start, end) / 2); - //sort the first half within an recursion - - std::thread t1([&]() { - ms_split(output_vec, start, mid, cmp, output_start, nthreads, dataset_guard, depth_guard); - }); - - //move output iteratoroutput_vec, start, mid, cmp, output_start - std::advance(output_start, std::distance(start, mid + 1)); - //sort the second half within a recursion - std::thread t2([&]() { - ms_split(output_vec, mid + 1, end, cmp, output_start, nthreads, dataset_guard, depth_guard); - }); - - - //merge everything together starting from the complete beginning - t1.join(); - t2.join(); - merge(start, mid, end, cmp, begin, dataset_guard); - } else { - //move mid iterator litterally to the mid - std::advance(mid, std::distance(start, end) / 2); - //sort the first half within an recursion - ms_split(output_vec, start, mid, cmp, output_start, nthreads, dataset_guard, depth_guard); - - //move output iterator - std::advance(output_start, std::distance(start, mid + 1)); - //sort the second half within a recursion - ms_split(output_vec, mid + 1, end, cmp, output_start, nthreads, dataset_guard, depth_guard); - - //merge everything together starting from the complete beginning - merge(start, mid, end, cmp, begin, dataset_guard); - } - } - } - - public: - template - static auto sort(Iterator start, Iterator end, Comparator cmp, int nthreads) -> void { - using valtype = typename std::iterator_traits::value_type; - std::vector temporary_dataset(std::distance(start, end)); - std::recursive_mutex dataset_guard; - std::mutex depth_guard; - - mt_split(temporary_dataset, start, end - 1, cmp, temporary_dataset.begin(), nthreads, dataset_guard, - depth_guard); - } - }; - - class MergeSort_mt { - - template - static auto - merge(std::vector left, std::vector right, - Comparator cmp, std::mutex &mut) -> std::vector { - - std::vector output; - output.reserve(left.size() + right.size()); - - auto l = left.begin(); - auto r = right.begin(); - - auto o = output.begin(); - - while (l < left.end() && r < right.end()) { - if (cmp(*l, *r)) { - output.insert(o, *l); - l++; - } else { - output.insert(o, *r); - r++; - } - o++; - } - while (l < left.end()) { - output.insert(o, *l); - o++; - l++; - } - while (r < right.end()) { - output.insert(o, *r); - o++; - r++; - } - return output; - } - - template - static auto split(std::vector data, Comparator cmp, int depth, int &max_depth, - std::mutex &mut) -> std::vector{ - - if (data.size() <= 1) { - return data; - } else if (data.size() == 2) { - if(cmp(data[0], data[1])) { - return std::vector {data[0], data[1]}; - } else { - return std::vector {data[1], data[0]}; - } - } - - std::vector output; - output.reserve(data.size()); - - auto mid = data.begin(); - std::advance(mid, std::distance(data.begin(), data.end()) / 2); - - - std::vector left(data.begin(), mid); - std::vector right(mid, data.end()); - - if (depth < max_depth) { - std::thread left_thread([&]() { left = split(left, cmp, depth + 1, max_depth, mut); }); - std::thread right_thread([&]() { right = split(right, cmp, depth + 1, max_depth, mut); }); - - left_thread.join(); - right_thread.join(); - } else { - left = split(left, cmp, depth + 1, max_depth, mut); - right = split(right, cmp, depth + 1, max_depth, mut); - } - - return merge(left, right, cmp, mut); - } - - public: - template - static auto - sort(std::vector &data, Comparator cmp, int max_depth = 0) -> void { - std::mutex local_result_lock; - std::vector output; - output.reserve(data.size()); - - output = split(data, cmp, 0, max_depth, local_result_lock); - data.assign(output.begin(), output.end()); - } - }; -} \ No newline at end of file diff --git a/task1/main.cpp b/task1/task1-auto.cpp similarity index 99% rename from task1/main.cpp rename to task1/task1-auto.cpp index 098935e..22aea6f 100644 --- a/task1/main.cpp +++ b/task1/task1-auto.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "include/mergesort_mt.h" /* Create a simple sorting application that uses the mergesort algorithm to sort a @@ -32,7 +32,6 @@ auto parse_file(std::ifstream &stream, std::vector &vec) -> void { convbuf = static_cast(std::stoul(buf)); vec.emplace_back(std::move(convbuf)); } - } auto main(int argc, char *argv[]) -> int {