diff --git a/13comp/Makefile b/13comp/Makefile new file mode 100644 index 0000000..1ec5d82 --- /dev/null +++ b/13comp/Makefile @@ -0,0 +1,25 @@ +CXX := g++ +CXXFLAGS := -O2 -std=c++17 -Wall -Wextra -pedantic +PYTHON := $(abspath ../.venv/bin/python) +OUT_DIR := out + +.PHONY: all run test export clean + +all: shm_sort pipe_sort + +shm_sort: shm_sort.cpp + $(CXX) $(CXXFLAGS) shm_sort.cpp -o shm_sort + +pipe_sort: pipe_sort.cpp + $(CXX) $(CXXFLAGS) pipe_sort.cpp -o pipe_sort + +run: all + $(PYTHON) exporter.py --out $(OUT_DIR) + +test: run + +export: run + +clean: + rm -f shm_sort pipe_sort + rm -rf $(OUT_DIR) __pycache__ diff --git a/13comp/exporter.py b/13comp/exporter.py new file mode 100644 index 0000000..ee3d8b8 --- /dev/null +++ b/13comp/exporter.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import csv +import os +import re +import subprocess +from pathlib import Path + +os.environ.setdefault("MPLCONFIGDIR", str(Path("out") / ".matplotlib")) + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +STAT_RE = re.compile( + r"STAT: program=(\w+) size=(\d+) depth=(\d+) min_size=(\d+) valid=(\d+) time=([\d.eE+-]+)" +) + + +def seed_for(size: int, repeat: int) -> int: + return 13013 + size * 17 + repeat * 1_000_003 + + +def run_once(binary: str, program: str, size: int, depth: int, min_size: int, seed: int) -> dict: + cmd = [ + binary, + "--size", + str(size), + "--depth", + str(depth), + "--min-size", + str(min_size), + "--seed", + str(seed), + ] + + proc = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True) + + if proc.returncode != 0: + raise RuntimeError("Command failed: " + " ".join(cmd) + "\n" + proc.stderr) + + match = STAT_RE.search(proc.stderr) + if not match: + raise RuntimeError("STAT line not found: " + proc.stderr) + + stat_program, stat_size, stat_depth, stat_min_size, valid, elapsed = match.groups() + + if stat_program != program: + raise RuntimeError(f"expected program={program}, got {stat_program}") + if valid != "1": + raise RuntimeError("sort validation failed: " + proc.stderr) + + return { + "program": program, + "size": int(stat_size), + "depth": int(stat_depth), + "min_size": int(stat_min_size), + "seed": seed, + "time": float(elapsed), + } + + +def write_csv(path: Path, rows: list[dict]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + header = ["program", "size", "depth", "min_size", "seed", "time"] + + with path.open("w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=header) + writer.writeheader() + writer.writerows(rows) + + +def draw_graph(path: Path, rows: list[dict]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + + sizes = sorted({row["size"] for row in rows}) + programs = ["shm", "pipe"] + markers = {"shm": "o", "pipe": "s"} + labels = {"shm": "Разделяемая память", "pipe": "Каналы"} + + fig, (time_ax, speed_ax) = plt.subplots(1, 2, figsize=(15, 6)) + + for program in programs: + cur = [row for row in rows if row["program"] == program] + cur.sort(key=lambda row: row["size"]) + time_ax.plot( + [row["size"] for row in cur], + [row["time"] for row in cur], + marker=markers[program], + label=labels[program], + ) + + for program in programs: + cur = [row for row in rows if row["program"] == program] + cur.sort(key=lambda row: row["size"]) + speed_ax.plot( + [row["size"] for row in cur], + [row["size"] / row["time"] if row["time"] > 0 else 0 for row in cur], + marker=markers[program], + label=labels[program], + ) + + time_ax.set_xscale("log", base=2) + speed_ax.set_xscale("log", base=2) + + time_ax.set_xlabel("Размер списка") + time_ax.set_ylabel("Время, секунды") + time_ax.set_title("Время сортировки связного списка слиянием") + time_ax.grid(True) + time_ax.legend() + + speed_ax.set_xlabel("Размер списка") + speed_ax.set_ylabel("Элементов в секунду") + speed_ax.set_title("Скорость сортировки на одинаковых нагрузках") + speed_ax.grid(True) + speed_ax.legend() + + fig.suptitle("Разделяемая память и каналы: задача сортировки списка из лабораторной 1") + fig.tight_layout() + fig.savefig(path, dpi=150) + plt.close(fig) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--out", default="out", help="output directory") + parser.add_argument("--depth", type=int, default=3) + parser.add_argument("--min-size", type=int, default=4096) + parser.add_argument("--repeats", type=int, default=3) + parser.add_argument( + "--sizes", + type=int, + nargs="+", + default=[2**power for power in range(9, 19)], + ) + args = parser.parse_args() + + out_dir = Path(args.out) + rows = [] + + for size in args.sizes: + for program, binary in [("shm", "./shm_sort"), ("pipe", "./pipe_sort")]: + measurements = [] + + for repeat in range(args.repeats): + seed = seed_for(size, repeat) + row = run_once(binary, program, size, args.depth, args.min_size, seed) + measurements.append(row) + print( + f"{program}: size={size} repeat={repeat + 1}/{args.repeats} " + f"time={row['time']:.6f}", + flush=True, + ) + + best = min(measurements, key=lambda row: row["time"]) + rows.append(best) + + write_csv(out_dir / "benchmark.csv", rows) + draw_graph(out_dir / "speed_comparison.png", rows) + print(f"Saved {out_dir / 'benchmark.csv'}") + print(f"Saved {out_dir / 'speed_comparison.png'}") + + +if __name__ == "__main__": + main() diff --git a/13comp/pipe_sort b/13comp/pipe_sort new file mode 100755 index 0000000..3c0282b Binary files /dev/null and b/13comp/pipe_sort differ diff --git a/13comp/pipe_sort.cpp b/13comp/pipe_sort.cpp new file mode 100644 index 0000000..0c11509 --- /dev/null +++ b/13comp/pipe_sort.cpp @@ -0,0 +1,377 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr int NIL = -1; + +struct Node { + int32_t value; + int next; +}; + +struct Options { + size_t size = 100000; + int max_depth = 3; + size_t min_size = 4096; + unsigned seed = 1337; + bool print = false; +}; + +struct SortResult { + std::vector values; +}; + +static void throw_errno(const std::string& what) { + throw std::runtime_error(what + ": " + std::strerror(errno)); +} + +static void close_checked(int fd) { + if (fd >= 0) { + while (close(fd) < 0 && errno == EINTR) {} + } +} + +static void write_all(int fd, const void* data, size_t bytes) { + const char* p = static_cast(data); + + while (bytes > 0) { + ssize_t n = write(fd, p, bytes); + if (n < 0) { + if (errno == EINTR) continue; + throw_errno("write"); + } + if (n == 0) throw std::runtime_error("write returned zero"); + p += n; + bytes -= static_cast(n); + } +} + +static void read_all(int fd, void* data, size_t bytes) { + char* p = static_cast(data); + + while (bytes > 0) { + ssize_t n = read(fd, p, bytes); + if (n < 0) { + if (errno == EINTR) continue; + throw_errno("read"); + } + if (n == 0) throw std::runtime_error("unexpected EOF"); + p += n; + bytes -= static_cast(n); + } +} + +static void send_values(int fd, const std::vector& values) { + uint64_t n = static_cast(values.size()); + write_all(fd, &n, sizeof(n)); + if (!values.empty()) write_all(fd, values.data(), values.size() * sizeof(values[0])); +} + +static std::vector recv_values(int fd) { + uint64_t n = 0; + read_all(fd, &n, sizeof(n)); + + if (n > static_cast(SIZE_MAX / sizeof(int32_t))) { + throw std::runtime_error("input too large"); + } + + std::vector values(static_cast(n)); + if (!values.empty()) read_all(fd, values.data(), values.size() * sizeof(values[0])); + return values; +} + +static int merge_lists(std::vector& nodes, int left, int right) { + if (left == NIL) return right; + if (right == NIL) return left; + + int head = NIL; + int tail = NIL; + + auto append = [&](int idx) { + if (head == NIL) { + head = idx; + tail = idx; + } else { + nodes[tail].next = idx; + tail = idx; + } + }; + + while (left != NIL && right != NIL) { + if (nodes[left].value <= nodes[right].value) { + int next = nodes[left].next; + append(left); + left = next; + } else { + int next = nodes[right].next; + append(right); + right = next; + } + } + + if (left != NIL) nodes[tail].next = left; + if (right != NIL) nodes[tail].next = right; + return head; +} + +static void split_list(std::vector& nodes, int head, int& left, int& right) { + if (head == NIL || nodes[head].next == NIL) { + left = head; + right = NIL; + return; + } + + int slow = head; + int fast = nodes[head].next; + + while (fast != NIL) { + fast = nodes[fast].next; + if (fast != NIL) { + slow = nodes[slow].next; + fast = nodes[fast].next; + } + } + + left = head; + right = nodes[slow].next; + nodes[slow].next = NIL; +} + +static int local_sort(std::vector& nodes, int head) { + if (head == NIL || nodes[head].next == NIL) return head; + + int left = NIL; + int right = NIL; + split_list(nodes, head, left, right); + left = local_sort(nodes, left); + right = local_sort(nodes, right); + return merge_lists(nodes, left, right); +} + +static std::vector make_nodes(const std::vector& values) { + std::vector nodes(values.size()); + + for (size_t i = 0; i < values.size(); ++i) { + nodes[i].value = values[i]; + nodes[i].next = (i + 1 == values.size()) ? NIL : static_cast(i + 1); + } + + return nodes; +} + +static std::vector collect_values(const std::vector& nodes, int head) { + std::vector out; + out.reserve(nodes.size()); + + for (int cur = head; cur != NIL; cur = nodes[cur].next) { + out.push_back(nodes[cur].value); + } + + return out; +} + +static std::vector sort_values_as_list(std::vector values) { + if (values.size() < 2) return values; + + std::vector nodes = make_nodes(values); + int head = local_sort(nodes, 0); + return collect_values(nodes, head); +} + +static void split_values_as_list(const std::vector& values, + std::vector& left_values, + std::vector& right_values) { + std::vector nodes = make_nodes(values); + + int left = NIL; + int right = NIL; + split_list(nodes, values.empty() ? NIL : 0, left, right); + + left_values = collect_values(nodes, left); + right_values = collect_values(nodes, right); +} + +static std::vector merge_values_as_list(const std::vector& left_values, + const std::vector& right_values) { + std::vector nodes; + nodes.reserve(left_values.size() + right_values.size()); + + int left = NIL; + int left_tail = NIL; + for (int32_t value : left_values) { + int idx = static_cast(nodes.size()); + nodes.push_back({value, NIL}); + if (left == NIL) left = idx; + else nodes[left_tail].next = idx; + left_tail = idx; + } + + int right = NIL; + int right_tail = NIL; + for (int32_t value : right_values) { + int idx = static_cast(nodes.size()); + nodes.push_back({value, NIL}); + if (right == NIL) right = idx; + else nodes[right_tail].next = idx; + right_tail = idx; + } + + int head = merge_lists(nodes, left, right); + return collect_values(nodes, head); +} + +static SortResult parallel_sort(std::vector values, int depth, const Options& opt); + +static pid_t spawn_child(const std::vector& part, + int depth, + const Options& opt, + int& result_fd) { + int to_child[2] = {-1, -1}; + int from_child[2] = {-1, -1}; + + if (pipe(to_child) < 0) throw_errno("pipe to_child"); + if (pipe(from_child) < 0) throw_errno("pipe from_child"); + + pid_t pid = fork(); + if (pid < 0) throw_errno("fork"); + + if (pid == 0) { + try { + close_checked(to_child[1]); + close_checked(from_child[0]); + + std::vector input = recv_values(to_child[0]); + close_checked(to_child[0]); + + SortResult result = parallel_sort(std::move(input), depth, opt); + send_values(from_child[1], result.values); + close_checked(from_child[1]); + _exit(0); + } catch (...) { + _exit(2); + } + } + + close_checked(to_child[0]); + close_checked(from_child[1]); + send_values(to_child[1], part); + close_checked(to_child[1]); + + result_fd = from_child[0]; + return pid; +} + +static SortResult parallel_sort(std::vector values, int depth, const Options& opt) { + if (values.size() < 2 || depth >= opt.max_depth || values.size() <= opt.min_size) { + return {sort_values_as_list(std::move(values))}; + } + + std::vector left; + std::vector right; + split_values_as_list(values, left, right); + + int left_fd = -1; + int right_fd = -1; + pid_t left_pid = spawn_child(left, depth + 1, opt, left_fd); + pid_t right_pid = spawn_child(right, depth + 1, opt, right_fd); + + std::vector sorted_left = recv_values(left_fd); + std::vector sorted_right = recv_values(right_fd); + close_checked(left_fd); + close_checked(right_fd); + + int left_status = 0; + int right_status = 0; + while (waitpid(left_pid, &left_status, 0) < 0 && errno == EINTR) {} + while (waitpid(right_pid, &right_status, 0) < 0 && errno == EINTR) {} + + if (!WIFEXITED(left_status) || WEXITSTATUS(left_status) != 0) { + throw std::runtime_error("left child failed"); + } + if (!WIFEXITED(right_status) || WEXITSTATUS(right_status) != 0) { + throw std::runtime_error("right child failed"); + } + + return {merge_values_as_list(sorted_left, sorted_right)}; +} + +static Options parse_args(int argc, char** argv) { + Options opt; + + for (int i = 1; i < argc; ++i) { + std::string s = argv[i]; + + auto value = [&](const std::string& name) -> std::string { + if (i + 1 >= argc) throw std::runtime_error("missing value for " + name); + return argv[++i]; + }; + + if (s == "--size" || s == "-n") { + opt.size = std::stoull(value(s)); + } else if (s == "--depth" || s == "-d") { + opt.max_depth = std::stoi(value(s)); + } else if (s == "--min-size" || s == "-m") { + opt.min_size = std::stoull(value(s)); + } else if (s == "--seed") { + opt.seed = static_cast(std::stoul(value(s))); + } else if (s == "--print") { + opt.print = true; + } else if (s == "--help" || s == "-h") { + std::cout << "Usage: ./pipe_sort [--size N] [--depth D] [--min-size M] " + << "[--seed S] [--print]\n"; + std::exit(0); + } else { + throw std::runtime_error("unknown argument: " + s); + } + } + + if (opt.max_depth < 0) throw std::runtime_error("depth must be non-negative"); + if (opt.size == 0) throw std::runtime_error("size must be positive"); + return opt; +} + +int main(int argc, char** argv) { + try { + Options opt = parse_args(argc, argv); + std::vector values(opt.size); + + std::mt19937 rng(opt.seed); + std::uniform_int_distribution dist(-100000000, 100000000); + + for (int32_t& value : values) value = dist(rng); + + const auto t1 = std::chrono::steady_clock::now(); + SortResult result = parallel_sort(std::move(values), 0, opt); + const auto t2 = std::chrono::steady_clock::now(); + + const double elapsed = std::chrono::duration(t2 - t1).count(); + const bool ok = std::is_sorted(result.values.begin(), result.values.end()) && + result.values.size() == opt.size; + + if (opt.print) { + for (int32_t value : result.values) std::cout << value << ' '; + std::cout << '\n'; + } + + std::cerr << "STAT: program=pipe size=" << opt.size + << " depth=" << opt.max_depth + << " min_size=" << opt.min_size + << " valid=" << (ok ? 1 : 0) + << " time=" << elapsed << " sec\n"; + + return ok ? 0 : 3; + } catch (const std::exception& e) { + std::cerr << "ERROR: " << e.what() << "\n"; + return 1; + } +} diff --git a/13comp/shm_sort b/13comp/shm_sort new file mode 100755 index 0000000..e9d778b Binary files /dev/null and b/13comp/shm_sort differ diff --git a/13comp/shm_sort.cpp b/13comp/shm_sort.cpp new file mode 100644 index 0000000..19b83dd --- /dev/null +++ b/13comp/shm_sort.cpp @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr int NIL = -1; +constexpr int MAX_RESULT_SLOTS = 4096; + +struct Node { + int32_t value; + int next; +}; + +struct SharedData { + int results[MAX_RESULT_SLOTS]; + Node nodes[1]; +}; + +struct Options { + size_t size = 100000; + int max_depth = 3; + size_t min_size = 4096; + unsigned seed = 1337; + bool print = false; +}; + +struct SortResult { + int head = NIL; + uint64_t processes = 1; +}; + +static void throw_errno(const std::string& what) { + throw std::runtime_error(what + ": " + std::strerror(errno)); +} + +static int merge_lists(Node* nodes, int left, int right) { + if (left == NIL) return right; + if (right == NIL) return left; + + int head = NIL; + int tail = NIL; + + auto append = [&](int idx) { + if (head == NIL) { + head = idx; + tail = idx; + } else { + nodes[tail].next = idx; + tail = idx; + } + }; + + while (left != NIL && right != NIL) { + if (nodes[left].value <= nodes[right].value) { + int next = nodes[left].next; + append(left); + left = next; + } else { + int next = nodes[right].next; + append(right); + right = next; + } + } + + if (left != NIL) nodes[tail].next = left; + if (right != NIL) nodes[tail].next = right; + return head; +} + +static void split_list(Node* nodes, int head, int& left, int& right) { + if (head == NIL || nodes[head].next == NIL) { + left = head; + right = NIL; + return; + } + + int slow = head; + int fast = nodes[head].next; + + while (fast != NIL) { + fast = nodes[fast].next; + if (fast != NIL) { + slow = nodes[slow].next; + fast = nodes[fast].next; + } + } + + left = head; + right = nodes[slow].next; + nodes[slow].next = NIL; +} + +static int local_sort(Node* nodes, int head) { + if (head == NIL || nodes[head].next == NIL) return head; + + int left = NIL; + int right = NIL; + split_list(nodes, head, left, right); + left = local_sort(nodes, left); + right = local_sort(nodes, right); + return merge_lists(nodes, left, right); +} + +static size_t list_length(const Node* nodes, int head) { + size_t n = 0; + while (head != NIL) { + ++n; + head = nodes[head].next; + } + return n; +} + +static SortResult parallel_sort(SharedData* data, + int head, + int depth, + const Options& opt, + int slot) { + const size_t n = list_length(data->nodes, head); + + if (head == NIL || data->nodes[head].next == NIL || + depth >= opt.max_depth || n <= opt.min_size) { + return {local_sort(data->nodes, head), 1}; + } + + int left = NIL; + int right = NIL; + split_list(data->nodes, head, left, right); + + const int left_slot = slot * 2 + 1; + const int right_slot = slot * 2 + 2; + + if (right_slot >= MAX_RESULT_SLOTS) { + int sorted_left = local_sort(data->nodes, left); + int sorted_right = local_sort(data->nodes, right); + return {merge_lists(data->nodes, sorted_left, sorted_right), 1}; + } + + pid_t left_pid = fork(); + if (left_pid < 0) throw_errno("fork left"); + + if (left_pid == 0) { + try { + SortResult r = parallel_sort(data, left, depth + 1, opt, left_slot); + data->results[left_slot] = r.head; + _exit(0); + } catch (...) { + _exit(2); + } + } + + pid_t right_pid = fork(); + if (right_pid < 0) throw_errno("fork right"); + + if (right_pid == 0) { + try { + SortResult r = parallel_sort(data, right, depth + 1, opt, right_slot); + data->results[right_slot] = r.head; + _exit(0); + } catch (...) { + _exit(2); + } + } + + int left_status = 0; + int right_status = 0; + + while (waitpid(left_pid, &left_status, 0) < 0 && errno == EINTR) {} + while (waitpid(right_pid, &right_status, 0) < 0 && errno == EINTR) {} + + if (!WIFEXITED(left_status) || WEXITSTATUS(left_status) != 0) { + throw std::runtime_error("left child failed"); + } + if (!WIFEXITED(right_status) || WEXITSTATUS(right_status) != 0) { + throw std::runtime_error("right child failed"); + } + + return {merge_lists(data->nodes, data->results[left_slot], data->results[right_slot]), + static_cast((1ULL << (depth + 2)) - 1ULL)}; +} + +static Options parse_args(int argc, char** argv) { + Options opt; + + for (int i = 1; i < argc; ++i) { + std::string s = argv[i]; + + auto value = [&](const std::string& name) -> std::string { + if (i + 1 >= argc) throw std::runtime_error("missing value for " + name); + return argv[++i]; + }; + + if (s == "--size" || s == "-n") { + opt.size = std::stoull(value(s)); + } else if (s == "--depth" || s == "-d") { + opt.max_depth = std::stoi(value(s)); + } else if (s == "--min-size" || s == "-m") { + opt.min_size = std::stoull(value(s)); + } else if (s == "--seed") { + opt.seed = static_cast(std::stoul(value(s))); + } else if (s == "--print") { + opt.print = true; + } else if (s == "--help" || s == "-h") { + std::cout << "Usage: ./shm_sort [--size N] [--depth D] [--min-size M] " + << "[--seed S] [--print]\n"; + std::exit(0); + } else { + throw std::runtime_error("unknown argument: " + s); + } + } + + if (opt.max_depth < 0) throw std::runtime_error("depth must be non-negative"); + if (opt.size == 0) throw std::runtime_error("size must be positive"); + return opt; +} + +static bool is_sorted_list(const Node* nodes, int head, size_t expected) { + size_t seen = 0; + int32_t prev = 0; + bool first = true; + + while (head != NIL) { + if (!first && prev > nodes[head].value) return false; + first = false; + prev = nodes[head].value; + head = nodes[head].next; + ++seen; + } + + return seen == expected; +} + +int main(int argc, char** argv) { + int shmid = -1; + SharedData* data = reinterpret_cast(-1); + + try { + Options opt = parse_args(argc, argv); + const size_t shm_size = sizeof(SharedData) + sizeof(Node) * (opt.size - 1); + + shmid = shmget(IPC_PRIVATE, shm_size, IPC_CREAT | 0600); + if (shmid < 0) throw_errno("shmget"); + + data = static_cast(shmat(shmid, nullptr, 0)); + if (data == reinterpret_cast(-1)) throw_errno("shmat"); + + std::fill(data->results, data->results + MAX_RESULT_SLOTS, NIL); + + std::mt19937 rng(opt.seed); + std::uniform_int_distribution dist(-100000000, 100000000); + + for (size_t i = 0; i < opt.size; ++i) { + data->nodes[i].value = dist(rng); + data->nodes[i].next = (i + 1 == opt.size) ? NIL : static_cast(i + 1); + } + + const auto t1 = std::chrono::steady_clock::now(); + SortResult result = parallel_sort(data, 0, 0, opt, 0); + const auto t2 = std::chrono::steady_clock::now(); + + const double elapsed = std::chrono::duration(t2 - t1).count(); + const bool ok = is_sorted_list(data->nodes, result.head, opt.size); + + if (opt.print) { + for (int cur = result.head; cur != NIL; cur = data->nodes[cur].next) { + std::cout << data->nodes[cur].value << ' '; + } + std::cout << '\n'; + } + + std::cerr << "STAT: program=shm size=" << opt.size + << " depth=" << opt.max_depth + << " min_size=" << opt.min_size + << " valid=" << (ok ? 1 : 0) + << " time=" << elapsed << " sec\n"; + + shmdt(data); + shmctl(shmid, IPC_RMID, nullptr); + return ok ? 0 : 3; + } catch (const std::exception& e) { + if (data != reinterpret_cast(-1)) shmdt(data); + if (shmid >= 0) shmctl(shmid, IPC_RMID, nullptr); + std::cerr << "ERROR: " << e.what() << "\n"; + return 1; + } +}