add hella vibecoded 3
This commit is contained in:
+40
@@ -0,0 +1,40 @@
|
||||
CXX := g++
|
||||
CXXFLAGS := -O2 -std=c++17 -Wall -Wextra -pedantic
|
||||
TARGET := lab3
|
||||
OUT_DIR := out
|
||||
|
||||
.PHONY: all clean run test bench timelines pack
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): main.cpp
|
||||
$(CXX) $(CXXFLAGS) main.cpp -o $(TARGET)
|
||||
|
||||
run: $(TARGET)
|
||||
./$(TARGET) --size 100000 --depth 2 --min-size 4096
|
||||
|
||||
test: $(TARGET)
|
||||
python3 test_lab3.py
|
||||
./$(TARGET) --size 0 --depth 3 --min-size 1 >/dev/null
|
||||
./$(TARGET) --size 1 --depth 3 --min-size 1 >/dev/null
|
||||
./$(TARGET) --size 10000 --depth 0 --min-size 1 >/dev/null
|
||||
./$(TARGET) --size 10000 --depth 2 --min-size 128 >/dev/null
|
||||
./$(TARGET) --size 10000 --depth 3 --min-size 256 --seed 2026 >/dev/null
|
||||
./$(TARGET) --size 12345 --depth 4 --min-size 257 --seed 777 >/dev/null
|
||||
|
||||
bench: $(TARGET)
|
||||
python3 benchmark.py
|
||||
|
||||
timelines: $(TARGET)
|
||||
mkdir -p $(OUT_DIR)/logs $(OUT_DIR)/pics
|
||||
./$(TARGET) --size 2048 --depth 2 --min-size 64 --log > $(OUT_DIR)/logs/depth2.log 2>$(OUT_DIR)/logs/depth2.stat
|
||||
./$(TARGET) --size 4096 --depth 3 --min-size 64 --log > $(OUT_DIR)/logs/depth3.log 2>$(OUT_DIR)/logs/depth3.stat
|
||||
python3 exporter.py $(OUT_DIR)/logs/depth2.log $(OUT_DIR)/pics
|
||||
python3 exporter.py $(OUT_DIR)/logs/depth3.log $(OUT_DIR)/pics
|
||||
|
||||
pack: clean
|
||||
zip -r lab3_process_pipes.zip main.cpp Makefile benchmark.py exporter.py test_lab3.py README.md
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) lab3_process_pipes.zip
|
||||
rm -rf $(OUT_DIR) __pycache__
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Лабораторная работа 3. Процессы. Неименованные каналы
|
||||
|
||||
**Вариант 13: 72-01. Сортировка массива рекурсивным разделением.**
|
||||
|
||||
Программа реализует сортировку массива рекурсивным разделением с построением дерева процессов. Каждый процесс получает часть массива, при разрешенной глубине рекурсии делит ее на две части, создает двух потомков через `fork()` и обменивается с ними данными через неименованные каналы `pipe()`.
|
||||
|
||||
Входные данные во всех экспериментах считаются **полностью случайными**. Отдельного режима `mode` нет. Для повторяемости используется только параметр `--seed`.
|
||||
|
||||
## Алгоритм
|
||||
|
||||
1. Родитель генерирует массив случайных целых чисел.
|
||||
2. На каждом рекурсивном шаге массив делится на левую и правую половины.
|
||||
3. Для каждой половины создается дочерний процесс.
|
||||
4. Родитель передает дочернему процессу данные в формате:
|
||||
- `uint64_t count`;
|
||||
- `count` значений типа `int32_t`.
|
||||
5. Потомок сортирует полученную часть тем же алгоритмом.
|
||||
6. При достижении `max_depth` или `min_size` сортировка выполняется локально без новых процессов.
|
||||
7. Потомок возвращает результат родителю в формате:
|
||||
- `uint64_t count`;
|
||||
- `count` отсортированных значений;
|
||||
- `uint64_t processes` — число процессов в поддереве.
|
||||
8. Родитель выполняет слияние двух отсортированных частей.
|
||||
|
||||
Для одного потомка используются два канала: родитель → потомок и потомок → родитель. Так как потомков два, на рекурсивном узле создается четыре канала.
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
```bash
|
||||
make
|
||||
./lab3 --size 100000 --depth 2 --min-size 4096
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
BIN = "./lab3"
|
||||
OUT = "out"
|
||||
STAT_RE = re.compile(
|
||||
r"STAT:.*size=(\d+).*depth=(\d+).*min_size=(\d+).*processes=(\d+).*valid=(\d+).*time=([\d.]+)"
|
||||
)
|
||||
|
||||
|
||||
def run_once(size, depth, min_size, seed):
|
||||
cmd = [
|
||||
BIN,
|
||||
"--size",
|
||||
str(size),
|
||||
"--depth",
|
||||
str(depth),
|
||||
"--min-size",
|
||||
str(min_size),
|
||||
"--seed",
|
||||
str(seed),
|
||||
]
|
||||
p = subprocess.run(
|
||||
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(p.stderr)
|
||||
m = STAT_RE.search(p.stderr)
|
||||
if not m:
|
||||
raise RuntimeError(f"STAT not found: {p.stderr}")
|
||||
if m.group(5) != "1":
|
||||
raise RuntimeError(f"sort validation failed: {p.stderr}")
|
||||
return {
|
||||
"size": int(m.group(1)),
|
||||
"depth": int(m.group(2)),
|
||||
"min_size": int(m.group(3)),
|
||||
"processes": int(m.group(4)),
|
||||
"time": float(m.group(6)),
|
||||
}
|
||||
|
||||
|
||||
def seed_for(size, depth, min_size, salt):
|
||||
# Для каждой точки используется свой seed, поэтому вход всегда случайный,
|
||||
# но результаты можно воспроизвести.
|
||||
return 2026 + salt * 1_000_003 + size * 17 + depth * 1009 + min_size * 31
|
||||
|
||||
|
||||
def save_csv(path, rows, header):
|
||||
with open(path, "w", encoding="utf-8", newline="") as f:
|
||||
w = csv.DictWriter(f, fieldnames=header)
|
||||
w.writeheader()
|
||||
w.writerows(rows)
|
||||
|
||||
|
||||
def plot_depth_scaling():
|
||||
os.makedirs(f"{OUT}/pics", exist_ok=True)
|
||||
|
||||
# ВАЖНО: на графиках по глубине ровно 30 точек по оси X: 0..29.
|
||||
#
|
||||
# depth идет до 29, но реально число процессов не взорвется бесконечно,
|
||||
# потому что дальнейшее деление останавливает min_size.
|
||||
depths = list(range(30))
|
||||
|
||||
# Несколько размеров дают несколько линий на одном графике.
|
||||
sizes = [50_000, 100_000, 200_000]
|
||||
min_size = 4096
|
||||
|
||||
rows = []
|
||||
for size in sizes:
|
||||
for d in depths:
|
||||
seed = seed_for(size, d, min_size, salt=1)
|
||||
r = run_once(size, d, min_size, seed)
|
||||
row = {**r, "seed": seed}
|
||||
rows.append(row)
|
||||
print(
|
||||
f"size={size} depth={d} min_size={min_size} "
|
||||
f"seed={seed} processes={r['processes']} time={r['time']:.6f}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
for size in sizes:
|
||||
cur = [r for r in rows if r["size"] == size]
|
||||
plt.plot(
|
||||
[r["depth"] for r in cur],
|
||||
[r["time"] for r in cur],
|
||||
marker="o",
|
||||
label=f"N={size}",
|
||||
)
|
||||
plt.xlabel("Глубина порождения процессов")
|
||||
plt.ylabel("Время, сек")
|
||||
plt.title("Зависимость времени сортировки от глубины fork-рекурсии")
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{OUT}/pics/time_by_depth.png")
|
||||
plt.close()
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
for size in sizes:
|
||||
cur = [r for r in rows if r["size"] == size]
|
||||
base_time = cur[0]["time"]
|
||||
speedup = [base_time / r["time"] if r["time"] > 0 else 0 for r in cur]
|
||||
plt.plot(
|
||||
[r["depth"] for r in cur],
|
||||
speedup,
|
||||
marker="s",
|
||||
label=f"N={size}",
|
||||
)
|
||||
plt.xlabel("Глубина порождения процессов")
|
||||
plt.ylabel("Ускорение относительно depth=0")
|
||||
plt.title("Ускорение при использовании процессов")
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{OUT}/pics/speedup_by_depth.png")
|
||||
plt.close()
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
for size in sizes:
|
||||
cur = [r for r in rows if r["size"] == size]
|
||||
plt.plot(
|
||||
[r["depth"] for r in cur],
|
||||
[r["processes"] for r in cur],
|
||||
marker="^",
|
||||
label=f"N={size}",
|
||||
)
|
||||
plt.xlabel("Глубина порождения процессов")
|
||||
plt.ylabel("Количество процессов")
|
||||
plt.title("Размер дерева процессов")
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{OUT}/pics/process_count_by_depth.png")
|
||||
plt.close()
|
||||
|
||||
save_csv(
|
||||
f"{OUT}/benchmark_depth.csv",
|
||||
rows,
|
||||
["size", "depth", "min_size", "seed", "processes", "time"],
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def plot_threshold_effect():
|
||||
os.makedirs(f"{OUT}/pics", exist_ok=True)
|
||||
|
||||
# ВАЖНО: на графике по min_size ровно 30 точек по оси X.
|
||||
size = 200_000
|
||||
depth = 5
|
||||
|
||||
# 30 значений порога от 128 до 131072, примерно равномерно по log2-шкале.
|
||||
min_sizes = [round(2 ** (7 + i * (10 / 29))) for i in range(30)]
|
||||
|
||||
rows = []
|
||||
for m in min_sizes:
|
||||
seed = seed_for(size, depth, m, salt=2)
|
||||
r = run_once(size, depth, m, seed)
|
||||
row = {**r, "seed": seed}
|
||||
rows.append(row)
|
||||
print(
|
||||
f"size={size} depth={depth} min_size={m} "
|
||||
f"seed={seed} processes={r['processes']} time={r['time']:.6f}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.plot(
|
||||
[r["min_size"] for r in rows],
|
||||
[r["time"] for r in rows],
|
||||
marker="o",
|
||||
)
|
||||
plt.xscale("log", base=2)
|
||||
plt.xlabel("Минимальный размер части для fork")
|
||||
plt.ylabel("Время, сек")
|
||||
plt.title("Влияние порога min_size на производительность")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{OUT}/pics/time_by_min_size.png")
|
||||
plt.close()
|
||||
|
||||
save_csv(
|
||||
f"{OUT}/benchmark_min_size.csv",
|
||||
rows,
|
||||
["size", "depth", "min_size", "seed", "processes", "time"],
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
plot_depth_scaling()
|
||||
plot_threshold_effect()
|
||||
print(f"Графики сохранены в {OUT}/pics. На каждом графике ровно 30 точек по оси X.")
|
||||
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Использование: python3 exporter.py <logfile> <output_dir>")
|
||||
sys.exit(1)
|
||||
|
||||
logfile = sys.argv[1]
|
||||
out_dir = sys.argv[2]
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
base = os.path.splitext(os.path.basename(logfile))[0]
|
||||
|
||||
pattern = re.compile(
|
||||
r"(START|END) PID=(\d+) PPID=(\d+) depth=(\d+) size=(\d+) time=([\d.]+)"
|
||||
)
|
||||
events = defaultdict(dict)
|
||||
|
||||
with open(logfile, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
m = pattern.search(line)
|
||||
if not m:
|
||||
continue
|
||||
typ, pid, ppid, depth, size, t = m.groups()
|
||||
key = (int(pid), int(depth), int(size))
|
||||
events[key][typ] = float(t)
|
||||
events[key]["pid"] = int(pid)
|
||||
events[key]["ppid"] = int(ppid)
|
||||
events[key]["depth"] = int(depth)
|
||||
events[key]["size"] = int(size)
|
||||
|
||||
rows = []
|
||||
for v in events.values():
|
||||
if "START" in v and "END" in v:
|
||||
rows.append(v)
|
||||
|
||||
if not rows:
|
||||
print("В логе нет полных START/END событий")
|
||||
sys.exit(1)
|
||||
|
||||
rows.sort(key=lambda r: (r["START"], r["depth"], r["pid"]))
|
||||
t0 = min(r["START"] for r in rows)
|
||||
|
||||
# 1. Временная диаграмма: видно параллельность и время жизни каждого процесса.
|
||||
plt.figure(figsize=(12, max(5, len(rows) * 0.35)))
|
||||
for y, r in enumerate(rows):
|
||||
start = r["START"] - t0
|
||||
end = r["END"] - t0
|
||||
plt.plot([start, end], [y, y], linewidth=5)
|
||||
plt.text(
|
||||
end,
|
||||
y,
|
||||
f" pid={r['pid']} d={r['depth']} n={r['size']}",
|
||||
va="center",
|
||||
fontsize=8,
|
||||
)
|
||||
|
||||
plt.xlabel("Время от начала, сек")
|
||||
plt.ylabel("Процессы/задачи сортировки")
|
||||
plt.title(f"Временная диаграмма процессов: {base}")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(os.path.join(out_dir, f"{base}_timeline.png"))
|
||||
plt.close()
|
||||
|
||||
# 2. Гистограмма глубин: проверка, что дерево дошло до нужной глубины.
|
||||
by_depth = defaultdict(int)
|
||||
for r in rows:
|
||||
by_depth[r["depth"]] += 1
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
xs = sorted(by_depth)
|
||||
plt.bar(xs, [by_depth[x] for x in xs])
|
||||
plt.xlabel("Глубина рекурсии")
|
||||
plt.ylabel("Количество процессов")
|
||||
plt.title(f"Распределение процессов по глубине: {base}")
|
||||
plt.grid(True, axis="y")
|
||||
plt.tight_layout()
|
||||
plt.savefig(os.path.join(out_dir, f"{base}_depth_hist.png"))
|
||||
plt.close()
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
// Лабораторная работа 3. Процессы. Неименованные каналы.
|
||||
// Вариант 13: 72-01. Сортировка массива рекурсивным разделением.
|
||||
//
|
||||
// Идея: родительский процесс делит массив на две части, создает двух потомков,
|
||||
// передает им части массива через pipe, потомки сортируют свои части тем же
|
||||
// алгоритмом до заданной глубины. При достижении max_depth или min_size сортировка
|
||||
// выполняется локально в одном процессе. Обратно потомки возвращают отсортированные
|
||||
// массивы тоже через pipe. Родитель выполняет слияние.
|
||||
|
||||
using i32 = int32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
struct Options {
|
||||
size_t size = 100000;
|
||||
int max_depth = 2;
|
||||
size_t min_size = 4096;
|
||||
unsigned seed = 1337;
|
||||
bool print = false;
|
||||
bool log = false;
|
||||
};
|
||||
|
||||
struct SortResult {
|
||||
std::vector<i32> data;
|
||||
u64 processes = 1; // текущий процесс тоже считается
|
||||
};
|
||||
|
||||
static double now_seconds() {
|
||||
using clock = std::chrono::steady_clock;
|
||||
static const auto start = clock::now();
|
||||
auto t = clock::now() - start;
|
||||
return std::chrono::duration<double>(t).count();
|
||||
}
|
||||
|
||||
static void log_event(const char* type, int depth, size_t n) {
|
||||
// Одна строка короче PIPE_BUF, поэтому при выводе в общий файл обычно не рвется.
|
||||
std::ostringstream ss;
|
||||
ss << type
|
||||
<< " PID=" << static_cast<long>(getpid())
|
||||
<< " PPID=" << static_cast<long>(getppid())
|
||||
<< " depth=" << depth
|
||||
<< " size=" << n
|
||||
<< " time=" << now_seconds()
|
||||
<< "\n";
|
||||
const std::string s = ss.str();
|
||||
(void)!write(STDOUT_FILENO, s.data(), s.size());
|
||||
}
|
||||
|
||||
[[noreturn]] static void die_child(const std::string& msg) {
|
||||
std::cerr << "CHILD_ERROR pid=" << getpid() << " " << msg << "\n";
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
static void throw_errno(const std::string& what) {
|
||||
throw std::runtime_error(what + ": " + std::strerror(errno));
|
||||
}
|
||||
|
||||
static void write_all(int fd, const void* ptr, size_t bytes) {
|
||||
const char* p = static_cast<const char*>(ptr);
|
||||
while (bytes > 0) {
|
||||
ssize_t w = write(fd, p, bytes);
|
||||
if (w < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw_errno("write");
|
||||
}
|
||||
if (w == 0) throw std::runtime_error("write returned 0");
|
||||
p += w;
|
||||
bytes -= static_cast<size_t>(w);
|
||||
}
|
||||
}
|
||||
|
||||
static void read_all(int fd, void* ptr, size_t bytes) {
|
||||
char* p = static_cast<char*>(ptr);
|
||||
while (bytes > 0) {
|
||||
ssize_t r = read(fd, p, bytes);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw_errno("read");
|
||||
}
|
||||
if (r == 0) throw std::runtime_error("unexpected EOF in pipe");
|
||||
p += r;
|
||||
bytes -= static_cast<size_t>(r);
|
||||
}
|
||||
}
|
||||
|
||||
static void close_checked(int fd) {
|
||||
if (fd >= 0) {
|
||||
while (close(fd) < 0 && errno == EINTR) {}
|
||||
}
|
||||
}
|
||||
|
||||
static void send_vector(int fd, const std::vector<i32>& a) {
|
||||
u64 n = static_cast<u64>(a.size());
|
||||
write_all(fd, &n, sizeof(n));
|
||||
if (!a.empty()) write_all(fd, a.data(), a.size() * sizeof(i32));
|
||||
}
|
||||
|
||||
static std::vector<i32> recv_vector(int fd) {
|
||||
u64 n = 0;
|
||||
read_all(fd, &n, sizeof(n));
|
||||
if (n > static_cast<u64>(SIZE_MAX / sizeof(i32))) {
|
||||
throw std::runtime_error("too large vector in pipe");
|
||||
}
|
||||
std::vector<i32> a(static_cast<size_t>(n));
|
||||
if (!a.empty()) read_all(fd, a.data(), a.size() * sizeof(i32));
|
||||
return a;
|
||||
}
|
||||
|
||||
static void send_result(int fd, const SortResult& result) {
|
||||
send_vector(fd, result.data);
|
||||
write_all(fd, &result.processes, sizeof(result.processes));
|
||||
}
|
||||
|
||||
static SortResult recv_result(int fd) {
|
||||
SortResult r;
|
||||
r.data = recv_vector(fd);
|
||||
read_all(fd, &r.processes, sizeof(r.processes));
|
||||
return r;
|
||||
}
|
||||
|
||||
static std::vector<i32> merge_sorted(const std::vector<i32>& left, const std::vector<i32>& right) {
|
||||
std::vector<i32> out;
|
||||
out.reserve(left.size() + right.size());
|
||||
size_t i = 0, j = 0;
|
||||
while (i < left.size() && j < right.size()) {
|
||||
if (left[i] <= right[j]) out.push_back(left[i++]);
|
||||
else out.push_back(right[j++]);
|
||||
}
|
||||
out.insert(out.end(), left.begin() + static_cast<long>(i), left.end());
|
||||
out.insert(out.end(), right.begin() + static_cast<long>(j), right.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
static void sequential_recursive_sort(std::vector<i32>& a) {
|
||||
if (a.size() < 2) return;
|
||||
const size_t mid = a.size() / 2;
|
||||
std::vector<i32> left(a.begin(), a.begin() + static_cast<long>(mid));
|
||||
std::vector<i32> right(a.begin() + static_cast<long>(mid), a.end());
|
||||
sequential_recursive_sort(left);
|
||||
sequential_recursive_sort(right);
|
||||
a = merge_sorted(left, right);
|
||||
}
|
||||
|
||||
static SortResult process_recursive_sort(std::vector<i32> a, int depth, const Options& opt);
|
||||
|
||||
static pid_t spawn_sort_child(const std::vector<i32>& part,
|
||||
int child_depth,
|
||||
const Options& opt,
|
||||
int& result_read_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<i32> input = recv_vector(to_child[0]);
|
||||
close_checked(to_child[0]);
|
||||
SortResult result = process_recursive_sort(std::move(input), child_depth, opt);
|
||||
send_result(from_child[1], result);
|
||||
close_checked(from_child[1]);
|
||||
_exit(0);
|
||||
} catch (const std::exception& e) {
|
||||
die_child(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
close_checked(to_child[0]);
|
||||
close_checked(from_child[1]);
|
||||
send_vector(to_child[1], part);
|
||||
close_checked(to_child[1]);
|
||||
result_read_fd = from_child[0];
|
||||
return pid;
|
||||
}
|
||||
|
||||
static SortResult process_recursive_sort(std::vector<i32> a, int depth, const Options& opt) {
|
||||
if (opt.log) log_event("START", depth, a.size());
|
||||
|
||||
if (a.size() < 2 || depth >= opt.max_depth || a.size() <= opt.min_size) {
|
||||
sequential_recursive_sort(a);
|
||||
if (opt.log) log_event("END", depth, a.size());
|
||||
return {std::move(a), 1};
|
||||
}
|
||||
|
||||
const size_t mid = a.size() / 2;
|
||||
std::vector<i32> left(a.begin(), a.begin() + static_cast<long>(mid));
|
||||
std::vector<i32> right(a.begin() + static_cast<long>(mid), a.end());
|
||||
|
||||
int left_fd = -1, right_fd = -1;
|
||||
pid_t left_pid = spawn_sort_child(left, depth + 1, opt, left_fd);
|
||||
pid_t right_pid = spawn_sort_child(right, depth + 1, opt, right_fd);
|
||||
|
||||
SortResult left_result = recv_result(left_fd);
|
||||
SortResult right_result = recv_result(right_fd);
|
||||
close_checked(left_fd);
|
||||
close_checked(right_fd);
|
||||
|
||||
int status_left = 0, status_right = 0;
|
||||
while (waitpid(left_pid, &status_left, 0) < 0 && errno == EINTR) {}
|
||||
while (waitpid(right_pid, &status_right, 0) < 0 && errno == EINTR) {}
|
||||
if (!WIFEXITED(status_left) || WEXITSTATUS(status_left) != 0) {
|
||||
throw std::runtime_error("left child failed");
|
||||
}
|
||||
if (!WIFEXITED(status_right) || WEXITSTATUS(status_right) != 0) {
|
||||
throw std::runtime_error("right child failed");
|
||||
}
|
||||
|
||||
SortResult result;
|
||||
result.data = merge_sorted(left_result.data, right_result.data);
|
||||
result.processes = 1 + left_result.processes + right_result.processes;
|
||||
|
||||
if (opt.log) log_event("END", depth, result.data.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
static Options parse_args(int argc, char** argv) {
|
||||
Options opt;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string s = argv[i];
|
||||
auto need_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(need_value(s));
|
||||
else if (s == "--depth" || s == "-d") opt.max_depth = std::stoi(need_value(s));
|
||||
else if (s == "--min-size" || s == "-m") opt.min_size = std::stoull(need_value(s));
|
||||
else if (s == "--seed") opt.seed = static_cast<unsigned>(std::stoul(need_value(s)));
|
||||
else if (s == "--print") opt.print = true;
|
||||
else if (s == "--log") opt.log = true;
|
||||
else if (s == "--help" || s == "-h") {
|
||||
std::cout << "Usage: ./lab3 [--size N] [--depth D] [--min-size M] [--seed S] "
|
||||
<< "[--print] [--log]\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");
|
||||
return opt;
|
||||
}
|
||||
|
||||
static std::vector<i32> generate_data(const Options& opt) {
|
||||
// По условию этой версии лабораторной входные данные всегда считаются
|
||||
// полностью случайными. Для повторяемости экспериментов используется --seed.
|
||||
std::vector<i32> a(opt.size);
|
||||
std::mt19937 rng(opt.seed);
|
||||
std::uniform_int_distribution<i32> dist(-100000000, 100000000);
|
||||
for (auto& x : a) x = dist(rng);
|
||||
return a;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
Options opt = parse_args(argc, argv);
|
||||
std::vector<i32> data = generate_data(opt);
|
||||
|
||||
const auto t1 = std::chrono::steady_clock::now();
|
||||
SortResult result = process_recursive_sort(std::move(data), 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.data.begin(), result.data.end());
|
||||
|
||||
if (opt.print) {
|
||||
for (size_t i = 0; i < result.data.size(); ++i) {
|
||||
if (i) std::cout << ' ';
|
||||
std::cout << result.data[i];
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
std::cerr << "STAT: size=" << opt.size
|
||||
<< " depth=" << opt.max_depth
|
||||
<< " min_size=" << opt.min_size
|
||||
<< " processes=" << result.processes
|
||||
<< " 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;
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
BIN = "./lab3"
|
||||
STAT_RE = re.compile(
|
||||
r"STAT:.*size=(\d+).*depth=(\d+).*min_size=(\d+).*processes=(\d+).*valid=(\d+).*time=([\d.]+)"
|
||||
)
|
||||
|
||||
|
||||
def run(args):
|
||||
p = subprocess.run([BIN, *args], text=True, capture_output=True)
|
||||
if p.returncode != 0:
|
||||
print("STDOUT:\n", p.stdout)
|
||||
print("STDERR:\n", p.stderr)
|
||||
raise AssertionError(f"command failed: {args}")
|
||||
m = STAT_RE.search(p.stderr)
|
||||
assert m and m.group(5) == "1", f"bad stat: {p.stderr}"
|
||||
return p, m
|
||||
|
||||
|
||||
def ints(s):
|
||||
return [int(x) for x in s.split()] if s.strip() else []
|
||||
|
||||
|
||||
def test_01_printed_small_random_array_sorted():
|
||||
p, _ = run(["--size", "97", "--depth", "3", "--min-size", "8", "--print"])
|
||||
a = ints(p.stdout)
|
||||
assert len(a) == 97
|
||||
assert a == sorted(a)
|
||||
|
||||
|
||||
def test_02_seed_makes_random_input_reproducible():
|
||||
a = run(
|
||||
["--size", "128", "--depth", "2", "--min-size", "8", "--seed", "42", "--print"]
|
||||
)[0].stdout
|
||||
b = run(
|
||||
["--size", "128", "--depth", "2", "--min-size", "8", "--seed", "42", "--print"]
|
||||
)[0].stdout
|
||||
c = run(
|
||||
["--size", "128", "--depth", "2", "--min-size", "8", "--seed", "43", "--print"]
|
||||
)[0].stdout
|
||||
assert a == b
|
||||
assert a != c
|
||||
|
||||
|
||||
def test_03_zero_depth_sequential_fallback():
|
||||
_, m = run(["--size", "1000", "--depth", "0", "--min-size", "1"])
|
||||
assert m.group(4) == "1"
|
||||
|
||||
|
||||
def test_04_depth_two_full_tree_process_count():
|
||||
_, m = run(["--size", "4096", "--depth", "2", "--min-size", "16"])
|
||||
assert m.group(4) == "7", m.group(0)
|
||||
|
||||
|
||||
def test_05_depth_three_full_tree_process_count():
|
||||
_, m = run(["--size", "8192", "--depth", "3", "--min-size", "16"])
|
||||
assert m.group(4) == "15", m.group(0)
|
||||
|
||||
|
||||
def test_06_min_size_stops_forking():
|
||||
_, m = run(["--size", "1000", "--depth", "5", "--min-size", "1000"])
|
||||
assert m.group(4) == "1", m.group(0)
|
||||
|
||||
|
||||
def test_07_odd_size_sorted_correctly():
|
||||
p, _ = run(
|
||||
["--size", "999", "--depth", "4", "--min-size", "17", "--seed", "77", "--print"]
|
||||
)
|
||||
a = ints(p.stdout)
|
||||
assert len(a) == 999
|
||||
assert a == sorted(a)
|
||||
|
||||
|
||||
def test_08_single_element_array():
|
||||
p, m = run(["--size", "1", "--depth", "5", "--min-size", "1", "--print"])
|
||||
a = ints(p.stdout)
|
||||
assert len(a) == 1
|
||||
assert m.group(4) == "1"
|
||||
|
||||
|
||||
def test_09_empty_array():
|
||||
p, m = run(["--size", "0", "--depth", "5", "--min-size", "1", "--print"])
|
||||
assert p.stdout.strip() == ""
|
||||
assert m.group(4) == "1"
|
||||
|
||||
|
||||
def test_10_timeline_log_has_events_and_pid_fields():
|
||||
p, _ = run(["--size", "128", "--depth", "2", "--min-size", "8", "--log"])
|
||||
assert "START PID=" in p.stdout
|
||||
assert "END PID=" in p.stdout
|
||||
assert "PPID=" in p.stdout
|
||||
assert "depth=" in p.stdout
|
||||
|
||||
|
||||
def test_11_help_has_no_mode_argument():
|
||||
p = subprocess.run([BIN, "--help"], text=True, capture_output=True)
|
||||
assert p.returncode == 0
|
||||
assert "--mode" not in p.stdout
|
||||
assert "--seed" in p.stdout
|
||||
|
||||
|
||||
def test_12_unknown_mode_is_rejected():
|
||||
p = subprocess.run([BIN, "--mode", "random"], text=True, capture_output=True)
|
||||
assert p.returncode != 0
|
||||
assert "unknown argument" in p.stderr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests = [
|
||||
test_01_printed_small_random_array_sorted,
|
||||
test_02_seed_makes_random_input_reproducible,
|
||||
test_03_zero_depth_sequential_fallback,
|
||||
test_04_depth_two_full_tree_process_count,
|
||||
test_05_depth_three_full_tree_process_count,
|
||||
test_06_min_size_stops_forking,
|
||||
test_07_odd_size_sorted_correctly,
|
||||
test_08_single_element_array,
|
||||
test_09_empty_array,
|
||||
test_10_timeline_log_has_events_and_pid_fields,
|
||||
test_11_help_has_no_mode_argument,
|
||||
test_12_unknown_mode_is_rejected,
|
||||
]
|
||||
for t in tests:
|
||||
t()
|
||||
print(f"OK {t.__name__}")
|
||||
Reference in New Issue
Block a user