add 13 comp
This commit is contained in:
@@ -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__
|
||||||
@@ -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()
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,377 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<int32_t> 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<const char*>(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<size_t>(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_all(int fd, void* data, size_t bytes) {
|
||||||
|
char* p = static_cast<char*>(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<size_t>(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_values(int fd, const std::vector<int32_t>& values) {
|
||||||
|
uint64_t n = static_cast<uint64_t>(values.size());
|
||||||
|
write_all(fd, &n, sizeof(n));
|
||||||
|
if (!values.empty()) write_all(fd, values.data(), values.size() * sizeof(values[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<int32_t> recv_values(int fd) {
|
||||||
|
uint64_t n = 0;
|
||||||
|
read_all(fd, &n, sizeof(n));
|
||||||
|
|
||||||
|
if (n > static_cast<uint64_t>(SIZE_MAX / sizeof(int32_t))) {
|
||||||
|
throw std::runtime_error("input too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int32_t> values(static_cast<size_t>(n));
|
||||||
|
if (!values.empty()) read_all(fd, values.data(), values.size() * sizeof(values[0]));
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int merge_lists(std::vector<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(std::vector<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(std::vector<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 std::vector<Node> make_nodes(const std::vector<int32_t>& values) {
|
||||||
|
std::vector<Node> 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<int>(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<int32_t> collect_values(const std::vector<Node>& nodes, int head) {
|
||||||
|
std::vector<int32_t> 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<int32_t> sort_values_as_list(std::vector<int32_t> values) {
|
||||||
|
if (values.size() < 2) return values;
|
||||||
|
|
||||||
|
std::vector<Node> nodes = make_nodes(values);
|
||||||
|
int head = local_sort(nodes, 0);
|
||||||
|
return collect_values(nodes, head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void split_values_as_list(const std::vector<int32_t>& values,
|
||||||
|
std::vector<int32_t>& left_values,
|
||||||
|
std::vector<int32_t>& right_values) {
|
||||||
|
std::vector<Node> 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<int32_t> merge_values_as_list(const std::vector<int32_t>& left_values,
|
||||||
|
const std::vector<int32_t>& right_values) {
|
||||||
|
std::vector<Node> 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<int>(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<int>(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<int32_t> values, int depth, const Options& opt);
|
||||||
|
|
||||||
|
static pid_t spawn_child(const std::vector<int32_t>& 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<int32_t> 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<int32_t> 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<int32_t> left;
|
||||||
|
std::vector<int32_t> 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<int32_t> sorted_left = recv_values(left_fd);
|
||||||
|
std::vector<int32_t> 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<unsigned>(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<int32_t> values(opt.size);
|
||||||
|
|
||||||
|
std::mt19937 rng(opt.seed);
|
||||||
|
std::uniform_int_distribution<int32_t> 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<double>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,295 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/ipc.h>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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<uint64_t>((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<unsigned>(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<SharedData*>(-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<SharedData*>(shmat(shmid, nullptr, 0));
|
||||||
|
if (data == reinterpret_cast<SharedData*>(-1)) throw_errno("shmat");
|
||||||
|
|
||||||
|
std::fill(data->results, data->results + MAX_RESULT_SLOTS, NIL);
|
||||||
|
|
||||||
|
std::mt19937 rng(opt.seed);
|
||||||
|
std::uniform_int_distribution<int32_t> 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<int>(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<double>(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<SharedData*>(-1)) shmdt(data);
|
||||||
|
if (shmid >= 0) shmctl(shmid, IPC_RMID, nullptr);
|
||||||
|
std::cerr << "ERROR: " << e.what() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user