From 63825f1849ca218ab04798b00ee660c977e715a3 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Mon, 28 Jul 2025 22:25:26 +0200 Subject: [PATCH] [init] --- .gitignore | 3 ++ .vscode/settings.json | 6 +++ CMakeLists.txt | 87 +++++++++++++++++++++++++++++++++++ CMakePresets.json | 19 ++++++++ Pipfile | 12 +++++ README.md | 44 ++++++++++++++++++ description.md | 2 + eval.py | 89 ++++++++++++++++++++++++++++++++++++ framework/runner.cpp | 103 ++++++++++++++++++++++++++++++++++++++++++ result.txt | 6 +++ src/container.cpp | 23 ++++++++++ src/container.hpp | 36 +++++++++++++++ src/sorter.cpp | 35 ++++++++++++++ src/sorter.hpp | 15 ++++++ 14 files changed, 480 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100755 CMakeLists.txt create mode 100755 CMakePresets.json create mode 100755 Pipfile create mode 100755 README.md create mode 100755 description.md create mode 100755 eval.py create mode 100755 framework/runner.cpp create mode 100644 result.txt create mode 100755 src/container.cpp create mode 100755 src/container.hpp create mode 100755 src/sorter.cpp create mode 100755 src/sorter.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e07682 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +cmake-build-* +.idea diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..672190d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "C_Cpp.default.compilerPath": "/usr/bin/gcc", + "files.associations": { + "span": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..8d77660 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.19) + +project(AlgEng-Uebung) + +find_package(Threads REQUIRED) + +add_executable(Sorter framework/runner.cpp + src/container.cpp src/container.hpp + src/sorter.cpp src/sorter.hpp) +target_link_libraries(Sorter PUBLIC Threads::Threads) + +target_compile_features(Sorter PRIVATE cxx_std_20) + +find_package( + Python + COMPONENTS Interpreter + REQUIRED) + +set(result_file ${CMAKE_CURRENT_LIST_DIR}/result.txt) + +add_custom_command( + OUTPUT ${result_file} + COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/eval.py run + --build-dir ${CMAKE_CURRENT_BINARY_DIR} ${result_file} + COMMENT "Running experiment" + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + DEPENDS Sorter eval.py) + +add_custom_target(run DEPENDS ${result_file}) + +set(plot_file ${CMAKE_CURRENT_LIST_DIR}/plot.pdf) + +add_custom_command( + OUTPUT ${plot_file} + COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/eval.py plot + ${result_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + COMMENT "Plotting results" + DEPENDS ${result_file} eval.py) + +add_custom_target(plot DEPENDS ${plot_file}) + +set(submission_file ${CMAKE_CURRENT_LIST_DIR}/submission.zip) + +add_custom_command( + OUTPUT ${submission_file} + COMMAND + ${CMAKE_COMMAND} -E tar cv ${submission_file} --format=zip + ${CMAKE_CURRENT_LIST_DIR}/src/container.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/container.hpp + ${CMAKE_CURRENT_LIST_DIR}/src/sorter.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/sorter.hpp + ${plot_file} + ${CMAKE_CURRENT_LIST_DIR}/description.md + COMMENT "Creating submission" + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + DEPENDS ${plot_file} ${CMAKE_CURRENT_LIST_DIR}/description.md) + + +add_custom_target(submission DEPENDS ${submission_file}) + +set(dist_file_list + CMakeLists.txt + CMakePresets.json + description.md + eval.py + framework/runner.cpp + Pipfile + README.md + src/container.cpp + src/container.hpp + src/sorter.cpp + src/sorter.hpp +) + +set(framework_dist_file "ae-sorting.zip") + +add_custom_command( + OUTPUT ${framework_dist_file} + COMMAND + ${CMAKE_COMMAND} -E tar cv ${framework_dist_file} --format=zip + ${dist_file_list} + COMMENT "Creating framework dist archive" + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} +) + +add_custom_target(framework DEPENDS ${framework_dist_file}) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100755 index 0000000..0036777 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "release", + "binaryDir": "${sourceDir}/build", + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "release", + "configurePreset": "release" + } + ] +} diff --git a/Pipfile b/Pipfile new file mode 100755 index 0000000..e315866 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +matplotlib = "*" + +[dev-packages] + +[requires] +python_version = "3.12" diff --git a/README.md b/README.md new file mode 100755 index 0000000..db5fa38 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Programming exercise + +Prerequisites: +- UNIX-like OS, or WSL for Windows +- `gcc`, `g++` >= 11 +- `CMake` >= 3.19 [^1] +- `python` and the package `matplotlib` (alternatively, you can use `pipenv` [^2] and the provided `Pipfile`) + +## Usage + +Implement your solution in `src/`, and describe it in `description.md`. +You can also adjust the evaluation code in `framework/` and `eval.py`, if you want. + +1. Compile your code using + +```console +cmake --preset release +cmake --build --preset release +``` + +2. Run the experiment via `eval.py run`. The output will be written to `result.txt`. +```console +python ./eval.py run result.txt +# or +cmake --build --preset release --target run +``` + +3. Create a plot via `eval.py plot`. This will create a PDF file named `plot.pdf`. +```console +python ./eval.py plot results.txt +# or +cmake --build --preset release --target plot +``` + +4. Describe your solution in `description.md`. + +5. Using `cmake`, create `submission.zip`, which will contain your source code, description and plots. +```console +cmake --build --preset release --target submission +``` + +[^1]: https://cmake.org/download/ +[^2]: https://pipenv.pypa.io/en/latest/ + diff --git a/description.md b/description.md new file mode 100755 index 0000000..4829dc6 --- /dev/null +++ b/description.md @@ -0,0 +1,2 @@ +# Description of your algorithm/datastructure +Describe/explain your design decisions briefly (~10 sentences). diff --git a/eval.py b/eval.py new file mode 100755 index 0000000..7d5eac5 --- /dev/null +++ b/eval.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +import matplotlib.pyplot as plt +import subprocess +import argparse +from pathlib import Path + +def run_experiment(output_file, build_dir): + # The number of threads is not currently used, it's just here in case you want to parallelize your code. + for threads in [1]: + for size in [1e2, 1e3, 1e4 + 1, 1e5, 1e6 - 1, 1e7]: + print("Measuring p=" + str(threads) + " n=" + str(size)) + executable = Path(build_dir) / "Sorter" + returncode = subprocess.call([executable, str(size), str(threads)], stdout=output_file) + if returncode != 0: + print("Program crashed") + +def make_plot(result_file): + plots = dict() + maxDuration = 0 + for line in result_file: + if line.startswith("RESULT"): + line = line[len("RESULT "):].strip() + parts = line.split() + measurement = {} + for part in parts: + key, value = part.split('=') + measurement[key] = value + + n = int(round(int(measurement["n"]), -1)) + t = int(measurement["t"]) + name = measurement["name"] + durationNanoseconds = int(measurement["durationNanoseconds"]) + constructorNanoseconds = int(measurement["constructorNanoseconds"]) + + if t not in plots: + plots[t] = dict() + if name not in plots[t]: + plots[t][name] = list() + plots[t][name + " (constructor)"] = list() + plots[t][name].append((n, durationNanoseconds / n)) + plots[t][name + " (constructor)"].append((n, constructorNanoseconds / n)) + maxDuration = max(maxDuration, max(durationNanoseconds, constructorNanoseconds)) + + fig, axs = plt.subplots(len(plots)) + + for i, t in enumerate(plots): + if len(plots) > 1: + axs[i].set_title(f"#p={t}") + for name in plots[t]: + axs[i].plot(*zip(*plots[t][name]), label=name, marker='x') + axs[i].plot(*zip(*plots[t][name + " (constructor)"]), label=name + " (constructor)", marker='+') + axs[i].set_xscale('log') + else: + axs.plot(*zip(*plots[t][name]), label=name, marker='x') + axs.plot(*zip(*plots[t][name + " (constructor)"]), label=name + " (constructor)", marker='+') + axs.set_xscale('log') + + if len(plots) > 1: + for ax in axs.flat: + ax.set(xlabel='n', ylabel='Running time per element (ns)') + ax.legend() + else: + axs.set(xlabel='n', ylabel='Running time per element (ns)') + axs.legend() + + plt.tight_layout() + + plt.savefig("plot.pdf") + +def main(): + parser = argparse.ArgumentParser(description='evaluation tools') + # subcommands run and plot + subparsers = parser.add_subparsers(dest='command') + run_parser = subparsers.add_parser('run') + run_parser.add_argument('output_file', type=argparse.FileType('w'), help='output file') + run_parser.add_argument("--build-dir", default="build", help="build directory") + plot_parser = subparsers.add_parser('plot') + plot_parser.add_argument('result_file', type=argparse.FileType('r'), help='result file') + args = parser.parse_args() + if args.command == 'run': + run_experiment(args.output_file, args.build_dir) + elif args.command == 'plot': + make_plot(args.result_file) + else: + parser.print_help() + +if __name__ == "__main__": + main() diff --git a/framework/runner.cpp b/framework/runner.cpp new file mode 100755 index 0000000..d509ba4 --- /dev/null +++ b/framework/runner.cpp @@ -0,0 +1,103 @@ +#include "../src/container.hpp" +#include "../src/sorter.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +auto generate_uniform(std::size_t n) { + std::vector input(n); + + std::mt19937 gen(n); + std::uniform_int_distribution dist; + std::ranges::generate(input, [&] { return dist(gen); }); + + return input; +} + +void runExperiment(std::string_view name, + auto container_factory, + auto sort_func, + int argc, char **argv) { + std::size_t n = 1e6; + std::size_t num_threads = 1; + + if (argc == 3) { + n = std::stol(argv[1]); + num_threads = std::stoi(argv[2]); + } else { + // The number of threads is just here in case you want to parallelize your code. + // It's not currently used. + std::cerr << "Number of threads not specified!\n"; + std::cerr << "Usage: " << argv[0] << " \n"; + return; + } + + const auto input = generate_uniform(n); + + const auto solution = [&] { + auto v = input; + std::ranges::sort(v); + return v; + }(); + + int iterations = 0; + long totalNanoseconds = 0; + long totalNanosecondsFactory = 0; + int maxIterations = 10'000; + + while (totalNanoseconds < 1000 * 1000) { + if (iterations >= maxIterations) { + break; + } + + std::chrono::steady_clock::time_point ctor = std::chrono::steady_clock::now(); + auto to_sort = container_factory(input); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + sort_func(to_sort); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + totalNanoseconds += + std::chrono::duration_cast(end - begin) + .count(); + totalNanosecondsFactory += + std::chrono::duration_cast(begin - ctor) + .count(); + iterations++; + + if (not std::ranges::equal(to_sort.to_view(), solution)) { + std::cerr << "Output of " << name << " is not correct!\n"; + return; + } + } + + std::cout << "RESULT" + << " name=" << name << " n=" << n << " t=" << num_threads + << " iterations=" << iterations + << " durationNanoseconds=" << totalNanoseconds / iterations + << " totalDurationNanoseconds=" << totalNanoseconds + << " constructorNanoseconds=" << totalNanosecondsFactory / iterations + << " totalConstructorNanoseconds=" << totalNanosecondsFactory + << '\n'; +} + +} // unnamed namespace + +int main(int argc, char **argv) { + runExperiment("sort", + [](const auto& data) { + return ae::container(data); + }, + [](ae::container& data) { + ae::sorter{}.sort(data); + }, argc, argv); + + return 0; +} diff --git a/result.txt b/result.txt new file mode 100644 index 0000000..7cdbe28 --- /dev/null +++ b/result.txt @@ -0,0 +1,6 @@ +RESULT name=sort n=100 t=1 iterations=1530 durationNanoseconds=653 totalDurationNanoseconds=1000022 constructorNanoseconds=233 totalConstructorNanoseconds=356900 +RESULT name=sort n=1000 t=1 iterations=76 durationNanoseconds=13288 totalDurationNanoseconds=1009961 constructorNanoseconds=598 totalConstructorNanoseconds=45480 +RESULT name=sort n=10001 t=1 iterations=3 durationNanoseconds=465803 totalDurationNanoseconds=1397411 constructorNanoseconds=26923 totalConstructorNanoseconds=80770 +RESULT name=sort n=100000 t=1 iterations=1 durationNanoseconds=6197415 totalDurationNanoseconds=6197415 constructorNanoseconds=790010 totalConstructorNanoseconds=790010 +RESULT name=sort n=999999 t=1 iterations=1 durationNanoseconds=67456368 totalDurationNanoseconds=67456368 constructorNanoseconds=5975124 totalConstructorNanoseconds=5975124 +RESULT name=sort n=10000000 t=1 iterations=1 durationNanoseconds=756668481 totalDurationNanoseconds=756668481 constructorNanoseconds=55741740 totalConstructorNanoseconds=55741740 diff --git a/src/container.cpp b/src/container.cpp new file mode 100755 index 0000000..cd6c694 --- /dev/null +++ b/src/container.cpp @@ -0,0 +1,23 @@ +#include "container.hpp" + +#include +#include + +namespace ae { + +container::container(std::span data) { + // TODO create your datastructure from the given data + + // The code below is a simple example splitting the data into 16 blocks, + // but you may find other options better suited for your sorting algorithm. + constexpr std::size_t num_blocks = 16; + const std::ptrdiff_t elements_per_block = (data.size() + num_blocks - 1) / num_blocks; + + for (auto first = data.begin(); first < data.end();) { + const auto last = (data.end() - first) < elements_per_block ? data.end() : first + elements_per_block; + placeholder_.emplace_back(first, last); + first = last; + } +} + +} // namespace ae diff --git a/src/container.hpp b/src/container.hpp new file mode 100755 index 0000000..92d9d4e --- /dev/null +++ b/src/container.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ae { + +class sorter; + +class container { + friend class sorter; + + public: + using element_type = std::uint64_t; + + explicit container(std::span data); + + // TODO You may also add additional functions (or data members). + + private: + // TODO define your data layout + // Your datastructure should consist of multiple blocks of data, which don't + // necessarily have to be vectors. + std::vector> placeholder_; + + public: + [[nodiscard]] auto to_view() const { + return std::views::join(placeholder_); + } +}; + +} // namespace ae diff --git a/src/sorter.cpp b/src/sorter.cpp new file mode 100755 index 0000000..9ef5d00 --- /dev/null +++ b/src/sorter.cpp @@ -0,0 +1,35 @@ +#include "sorter.hpp" + +#include +#include + +namespace ae { + +void sorter::sort(container& data) { + // TODO Implement your sorting algorithm + for (auto i = 1uz; i < data.placeholder_.size(); ++i) { + std::ranges::copy(data.placeholder_[i], std::back_inserter(data.placeholder_[0])); + data.placeholder_[i].clear(); + } + std::ranges::sort(data.placeholder_[0]); +} + +void sorter::msd_inplace_radix_sort(std::span range, size_t passes, size_t digit) { + if (digit == passes) { + return; + } + + auto lower = std::begin(range); + auto upper = std::end(range); + + for (auto element = lower; element < std::end(range);) { + // Mask out the -last bit and check if it is set + if ((*element & (1uz << sizeof(container::element_type) * 8 - digit))) { + + } else { + + } + } +} + +} // namespace ae diff --git a/src/sorter.hpp b/src/sorter.hpp new file mode 100755 index 0000000..b2d4268 --- /dev/null +++ b/src/sorter.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "container.hpp" + +namespace ae { + +class sorter { + public: + void sort(container& data); + + // TODO You may add additional functions or data members to the sorter. + void msd_inplace_radix_sort(std::span range, size_t passes, size_t digit = 0); +}; + +} // namespace ae