Skip to content

Commit 3e39c41

Browse files
authored
Merge pull request #345 from hitonanode/mhc-multithread
add ParallelRunner
2 parents 8cea727 + 864dc6b commit 3e39c41

File tree

3 files changed

+188
-65
lines changed

3 files changed

+188
-65
lines changed

multithread/multithread_example.cpp

Lines changed: 0 additions & 65 deletions
This file was deleted.

multithread/parallel_runner.hpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#ifndef PARALLEL_RUNNER_HPP
2+
#define PARALLEL_RUNNER_HPP
3+
4+
#include <chrono>
5+
#include <concepts>
6+
#include <exception>
7+
#include <future>
8+
#include <iostream>
9+
#include <mutex>
10+
#include <optional>
11+
#include <vector>
12+
13+
template <class T>
14+
concept ISolver = requires(const T ct, T t) {
15+
t = {};
16+
{ t.solve() } -> std::same_as<typename T::Ret>;
17+
{ t.read_stdin() } -> std::same_as<void>;
18+
{ ct.dump_stdout(std::declval<typename T::Ret>()) } -> std::same_as<void>;
19+
};
20+
21+
template <ISolver Solver> class ParallelRunner {
22+
public:
23+
int num_threads_;
24+
std::vector<Solver> instances;
25+
std::vector<std::optional<typename Solver::Ret>> rets;
26+
27+
std::mutex mtx;
28+
29+
ParallelRunner(int num_threads = std::thread::hardware_concurrency())
30+
: num_threads_(num_threads > 0 ? num_threads : 1) {
31+
std::cerr << "num_threads: " << num_threads_ << std::endl;
32+
}
33+
34+
void read_all(int num_testcases) {
35+
instances.clear();
36+
instances.reserve(num_testcases);
37+
for (int i = 0; i < num_testcases; ++i) {
38+
instances.emplace_back(Solver{});
39+
instances.back().read_stdin();
40+
}
41+
}
42+
43+
void run_sequential() {
44+
rets.assign(instances.size(), std::nullopt);
45+
46+
for (int index = 0; index < (int)instances.size(); ++index) {
47+
run_single_(index);
48+
if (rets.at(index).has_value()) {
49+
mhc_stdout_(instances.at(index), rets.at(index).value(), index);
50+
}
51+
}
52+
}
53+
54+
void run_parallel(int num_skip = 0) {
55+
rets.assign(instances.size(), std::nullopt);
56+
57+
const int num_inputs = instances.size();
58+
std::vector<std::future<void>> futures;
59+
60+
std::atomic<int> index(num_skip < 0 ? 0 : num_skip);
61+
std::vector<bool> is_finished(num_inputs, false);
62+
int num_written = 0;
63+
64+
for (int i = 0; i < num_threads_; ++i) {
65+
futures.push_back(std::async(std::launch::async, [&]() {
66+
while (true) {
67+
const int current_index = index.fetch_add(1);
68+
if (current_index >= num_inputs) break;
69+
70+
run_single_(current_index);
71+
72+
{
73+
std::unique_lock<std::mutex> lock(mtx);
74+
is_finished.at(current_index) = true;
75+
while (num_written < num_inputs and is_finished.at(num_written)) {
76+
if (rets.at(num_written).has_value()) {
77+
mhc_stdout_(instances.at(num_written),
78+
rets.at(num_written).value(), num_written);
79+
}
80+
++num_written;
81+
}
82+
}
83+
}
84+
}));
85+
}
86+
87+
for (auto &f : futures) f.get();
88+
}
89+
90+
void run_single_(int current_index) {
91+
{
92+
std::unique_lock<std::mutex> lock(mtx);
93+
std::cerr << "[#" << current_index + 1 << "] start" << std::endl;
94+
}
95+
96+
auto start = std::chrono::steady_clock::now();
97+
98+
try {
99+
rets.at(current_index) = instances.at(current_index).solve();
100+
} catch (const std::exception &e) {
101+
std::unique_lock<std::mutex> lock(mtx);
102+
std::cerr << "Error in Case #" << current_index + 1 << ": " << e.what() << std::endl;
103+
return;
104+
} catch (...) {
105+
std::unique_lock<std::mutex> lock(mtx);
106+
std::cerr << "Unknown error in Case #" << current_index + 1 << std::endl;
107+
return;
108+
}
109+
110+
auto end = std::chrono::steady_clock::now();
111+
auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
112+
113+
{
114+
std::unique_lock<std::mutex> lock(mtx);
115+
std::cerr << "[#" << current_index + 1 << "] end, " << msec << " ms" << std::endl;
116+
}
117+
}
118+
119+
static void mhc_stdout_(const Solver &result, const Solver::Ret &sol, int index) {
120+
std::cout << "Case #" << index + 1 << ": ";
121+
result.dump_stdout(sol);
122+
std::cout << std::flush;
123+
}
124+
};
125+
126+
#endif // PARALLEL_RUNNER_HPP
127+
128+
/* Usage:
129+
struct Solver {
130+
using Ret = int;
131+
132+
void read_stdin() {
133+
// read input using std::cin
134+
}
135+
136+
Ret solve() {
137+
// solve the problem
138+
}
139+
140+
void dump_stdout(const Ret &ret) const {
141+
// output the result using std::cout
142+
// std::cout << ret << std::endl;
143+
}
144+
};
145+
146+
int T;
147+
cin >> T;
148+
149+
ParallelRunner<Solver> pm;
150+
pm.read_all(T);
151+
pm.run_parallel();
152+
*/

multithread/parallel_runner.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
title: Parallel runner (複数テストケースのマルチスレッド並列実行)
3+
documentation_of: ./parallel_runner.hpp
4+
---
5+
6+
ダウンロードしたテストケースに対してプログラムを手元で実行して結果を提出する形式の競技において,複数テストケースの並列処理を行うためのコード.
7+
8+
## 使用例
9+
10+
``` cpp
11+
struct Solver {
12+
using Ret = int;
13+
14+
void read_stdin() {
15+
// read input using std::cin
16+
}
17+
18+
Ret solve() {
19+
// solve the problem
20+
}
21+
22+
void dump_stdout(const Ret &ret) const {
23+
// output the result using std::cout
24+
// std::cout << ret << std::endl;
25+
}
26+
};
27+
28+
int main() {
29+
int T;
30+
cin >> T;
31+
32+
ParallelRunner<Solver> pm;
33+
pm.read_all(T);
34+
pm.run_parallel();
35+
}
36+
```

0 commit comments

Comments
 (0)