This commit is contained in:
Patrick Schneider 2025-07-28 22:25:26 +02:00
commit 63825f1849
14 changed files with 480 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
cmake-build-*
.idea

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"C_Cpp.default.compilerPath": "/usr/bin/gcc",
"files.associations": {
"span": "cpp"
}
}

87
CMakeLists.txt Executable file
View File

@ -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})

19
CMakePresets.json Executable file
View File

@ -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"
}
]
}

12
Pipfile Executable file
View File

@ -0,0 +1,12 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
matplotlib = "*"
[dev-packages]
[requires]
python_version = "3.12"

44
README.md Executable file
View File

@ -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/

2
description.md Executable file
View File

@ -0,0 +1,2 @@
# Description of your algorithm/datastructure
Describe/explain your design decisions briefly (~10 sentences).

89
eval.py Executable file
View File

@ -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()

103
framework/runner.cpp Executable file
View File

@ -0,0 +1,103 @@
#include "../src/container.hpp"
#include "../src/sorter.hpp"
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <random>
#include <string>
#include <string_view>
#include <vector>
namespace {
auto generate_uniform(std::size_t n) {
std::vector<std::uint64_t> input(n);
std::mt19937 gen(n);
std::uniform_int_distribution<std::uint64_t> 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> <num_threads>\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<std::chrono::nanoseconds>(end - begin)
.count();
totalNanosecondsFactory +=
std::chrono::duration_cast<std::chrono::nanoseconds>(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;
}

6
result.txt Normal file
View File

@ -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

23
src/container.cpp Executable file
View File

@ -0,0 +1,23 @@
#include "container.hpp"
#include <cstddef>
#include <span>
namespace ae {
container::container(std::span<const element_type> 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

36
src/container.hpp Executable file
View File

@ -0,0 +1,36 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <ranges>
#include <span>
#include <vector>
namespace ae {
class sorter;
class container {
friend class sorter;
public:
using element_type = std::uint64_t;
explicit container(std::span<const element_type> 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<std::vector<element_type>> placeholder_;
public:
[[nodiscard]] auto to_view() const {
return std::views::join(placeholder_);
}
};
} // namespace ae

35
src/sorter.cpp Executable file
View File

@ -0,0 +1,35 @@
#include "sorter.hpp"
#include <algorithm>
#include <iterator>
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<container::element_type> 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 <digit>-last bit and check if it is set
if ((*element & (1uz << sizeof(container::element_type) * 8 - digit))) {
} else {
}
}
}
} // namespace ae

15
src/sorter.hpp Executable file
View File

@ -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<container::element_type> range, size_t passes, size_t digit = 0);
};
} // namespace ae