Skip to content

Commit 85d4d91

Browse files
committed
Adopt some of the improvements in subpar::parallelize.
This includes the use of exception_ptr instead of strings, as well as improved worksharing with more precise jobs-to-worker calculations.
1 parent b0dbc58 commit 85d4d91

3 files changed

Lines changed: 45 additions & 25 deletions

File tree

include/tatami_r/parallelize.hpp

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,67 +40,73 @@ inline manticore::Executor& executor() {
4040

4141
/**
4242
* @tparam Function_ Function to be executed.
43+
* @tparam Index_ Integer type for the job indices.
4344
*
44-
* @param njobs Number of jobs to be executed.
4545
* @param fun Function to run in each thread.
4646
* This is a lambda that should accept three arguments:
47-
* - Integer containing the thread ID
47+
* - Integer containing the thread ID.
4848
* - Integer specifying the index of the first job to be executed in a thread.
4949
* - Integer specifying the number of jobs to be executed in a thread.
50+
* @param njobs Number of jobs to be executed.
5051
* @param nthreads Number of threads to parallelize over.
5152
*
53+
* This function is a drop-in replacement for `tatami::parallelize()`.
5254
* The series of integers from 0 to `njobs - 1` is split into `nthreads` contiguous ranges.
5355
* Each range is used as input to `fun` within the corresponding thread.
5456
* It is assumed that the execution of any given job is independent of the next.
5557
*
5658
* This function is only available if `TATAMI_R_PARALLELIZE_UNKNOWN` is defined.
5759
*/
58-
template<class Function_>
59-
void parallelize(Function_ fun, size_t njobs, size_t nthreads) {
60-
if (nthreads == 1 || njobs == 1) {
60+
template<class Function_, class Index_>
61+
void parallelize(Function_ fun, Index_ njobs, int nthreads) {
62+
if (njobs == 0) {
63+
return;
64+
}
65+
66+
if (nthreads <= 1 || njobs == 1) {
6167
fun(0, 0, njobs);
6268
return;
6369
}
6470

71+
Index_ jobs_per_worker = njobs / nthreads;
72+
int remainder = njobs % nthreads;
73+
if (jobs_per_worker == 0) {
74+
jobs_per_worker = 1;
75+
remainder = 0;
76+
nthreads = njobs;
77+
}
78+
6579
auto& mexec = executor();
6680
mexec.initialize(nthreads, "failed to execute R command");
6781

68-
size_t jobs_per_worker = (njobs / nthreads) + (njobs % nthreads > 0);
69-
size_t start = 0;
70-
7182
std::vector<std::thread> runners;
7283
runners.reserve(nthreads);
73-
std::vector<std::string> errors(nthreads);
84+
std::vector<std::exception_ptr> errors(nthreads);
7485

75-
for (size_t w = 0; w < nthreads; ++w) {
76-
if (start == njobs) {
77-
mexec.finish_thread(false);
78-
continue;
79-
}
80-
size_t end = start + std::min(njobs - start, jobs_per_worker);
86+
Index_ start = 0;
87+
for (int w = 0; w < nthreads; ++w) {
88+
Index_ length = jobs_per_worker + (w < remainder);
8189

82-
runners.emplace_back([&](size_t id, size_t s, size_t l) -> void {
90+
runners.emplace_back([&](int id, Index_ s, Index_ l) {
8391
try {
8492
fun(id, s, l);
85-
} catch (std::exception& x) {
86-
// No throw here, we need to make sure we mark the
87-
// thread as being completed so that the main loop can quit.
88-
errors[id] = x.what();
93+
} catch (...) {
94+
errors[id] = std::current_exception();
8995
}
9096
mexec.finish_thread();
91-
}, w, start, end - start);
97+
}, w, start, length);
9298

93-
start = end;
99+
start += length;
94100
}
95101

96102
mexec.listen();
97103
for (auto& x : runners) {
98104
x.join();
99105
}
100106

101-
for (auto err : errors) {
102-
if (!err.empty()) {
103-
throw std::runtime_error(err);
107+
for (const auto& err : errors) {
108+
if (err) {
109+
std::rethrow_exception(err);
104110
}
105111
}
106112
}

tests/src/Makevars

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ PKG_CPPFLAGS=-I../../include \
22
-I../../build/_deps/tatami-src/include/ \
33
-I../../build/_deps/tatami_chunked-src/include/ \
44
-I../../build/_deps/manticore-src/include/ \
5+
-I../../build/_deps/subpar-src/include/ \
56
-DTEST_CUSTOM_PARALLEL \
67
-fstack-protector-strong \
78
-Wformat \

tests/tests/testthat/test-miscellaneous.R

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ test_that("works correctly with the default cache size", {
99
expect_identical(raticate.tests::num_columns(z), 20L)
1010
})
1111

12+
# parallelization handles edge cases correctly
13+
{
14+
library(Matrix)
15+
y <- Matrix(runif(1), 1, 1) # only one job in either dimension.
16+
parallel_test_suite(y, 0.1)
17+
18+
y <- Matrix(runif(0), 0, 0) # no jobs in either dimension.
19+
parallel_test_suite(y, 0.1)
20+
21+
y <- Matrix(runif(4), 2, 2) # fewer jobs than threads (for workers = 3).
22+
parallel_test_suite(y, 0.1)
23+
}
24+
1225
test_that("Behaves correctly with R-side errors", {
1326
setClass("MyFailMatrix", contains="dgeMatrix")
1427
setMethod("extract_array", "MyFailMatrix", function(x, index) stop("HEY!"))

0 commit comments

Comments
 (0)