[init]
This commit is contained in:
commit
63825f1849
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build
|
||||
cmake-build-*
|
||||
.idea
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"C_Cpp.default.compilerPath": "/usr/bin/gcc",
|
||||
"files.associations": {
|
||||
"span": "cpp"
|
||||
}
|
||||
}
|
||||
87
CMakeLists.txt
Executable file
87
CMakeLists.txt
Executable 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
19
CMakePresets.json
Executable 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
12
Pipfile
Executable 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
44
README.md
Executable 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
2
description.md
Executable file
@ -0,0 +1,2 @@
|
||||
# Description of your algorithm/datastructure
|
||||
Describe/explain your design decisions briefly (~10 sentences).
|
||||
89
eval.py
Executable file
89
eval.py
Executable 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
103
framework/runner.cpp
Executable 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
6
result.txt
Normal 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
23
src/container.cpp
Executable 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
36
src/container.hpp
Executable 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
35
src/sorter.cpp
Executable 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
15
src/sorter.hpp
Executable 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
|
||||
Loading…
x
Reference in New Issue
Block a user