Skip to content

Commit eadf1d8

Browse files
committed
Accept a RHS tatami::Matrix in the public dense_row x dense matrix functions.
This matches the dense row x dense matrix function signature in the dispatcher. Also cleaned up some of the variable names for clarity, and replace std::min with sanisizer::min to avoid hard-coding the expected input type.
1 parent d2b162d commit eadf1d8

5 files changed

Lines changed: 226 additions & 226 deletions

File tree

include/tatami_mult/dense_matrix/dense_row/column_to_column.hpp

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -58,44 +58,47 @@ struct MultiplyDenseRowWithDenseColumnMatrixToColumnOutputOptions {
5858
template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
5959
void multiply_dense_row_with_dense_column_matrix_to_column_output(
6060
const tatami::Matrix<LeftValue_, LeftIndex_>& left,
61-
const std::vector<const RightValue_*>& right,
62-
const RightIndex_ num_rhs,
61+
const tatami::Matrix<RightValue_, RightIndex_>& right,
6362
Output_* const output,
6463
const MultiplyDenseRowWithDenseColumnMatrixToColumnOutputOptions& options
6564
) {
66-
const auto NR = left.nrow();
67-
const auto NC = left.ncol();
65+
const auto left_NR = left.nrow();
66+
const auto common_dim = left.ncol();
67+
const auto right_NC = right.ncol();
68+
69+
auto right_buffers = tatami::create_container_of_Index_size<std::vector<std::vector<RightValue_> > >(right_NC);
70+
auto right_ptrs = tatami::create_container_of_Index_size<std::vector<const RightValue_*> >(right_NC);
71+
populate_dense_buffers(false, right_NC, common_dim, right, right_buffers, right_ptrs, options.num_threads);
6872

6973
if (options.primary_block_size == 1) {
7074
tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
7175
auto lext = tatami::consecutive_extractor<false>(left, true, start, length);
72-
auto lbuffer = tatami::create_container_of_Index_size<std::vector<LeftValue_> >(NC);
73-
for (LeftIndex_ r = 0; r < length; ++r) {
76+
auto lbuffer = tatami::create_container_of_Index_size<std::vector<LeftValue_> >(common_dim);
77+
for (LeftIndex_ lr = 0; lr < length; ++lr) {
7478
const auto lptr = lext->fetch(lbuffer.data());
75-
for (RightIndex_ h = 0; h < num_rhs; ++h) {
76-
output[sanisizer::nd_offset<std::size_t>(start + r, NR, h)] = dense_dot_product<accumulators_>(NC, lptr, right[h], static_cast<Output_>(0));
79+
for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
80+
// cast of common_dim to size_t is safe due to tatami's contract.
81+
const auto val = dense_dot_product<accumulators_>(common_dim, lptr, right_ptrs[rc], static_cast<Output_>(0));
82+
output[sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc)] = val;
7783
}
7884
}
79-
}, NR, options.num_threads);
85+
}, left_NR, options.num_threads);
8086
return;
8187
}
8288

83-
const auto primary_block_size = sanisizer::cast<LeftIndex_>(options.primary_block_size);
84-
const auto secondary_block_size = sanisizer::cast<LeftIndex_>(options.secondary_block_size);
85-
8689
const bool do_parallel = options.num_threads > 1;
8790
if (!do_parallel) {
88-
std::fill_n(output, sanisizer::product_unsafe<std::size_t>(NR, num_rhs), 0);
91+
std::fill_n(output, sanisizer::product_unsafe<std::size_t>(left_NR, right_NC), 0);
8992
}
9093

9194
tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
9295
auto ext = tatami::consecutive_extractor<false>(left, true, start, length);
9396

94-
const LeftIndex_ max_block_rows = std::min(length, primary_block_size);
97+
const LeftIndex_ max_block_rows = sanisizer::min(length, options.primary_block_size);
9598
std::vector<std::vector<LeftValue_> > lbuffers;
9699
lbuffers.reserve(max_block_rows);
97100
for (LeftIndex_ b = 0; b < max_block_rows; ++b) {
98-
lbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(NC));
101+
lbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
99102
}
100103
auto lptrs = tatami::create_container_of_Index_size<std::vector<const LeftValue_*> >(max_block_rows);
101104

@@ -106,70 +109,71 @@ void multiply_dense_row_with_dense_column_matrix_to_column_output(
106109
// This aims to mitigate false sharing as we update each block's partial dot products in the loop over the common dimension.
107110
// There is still some potential for false sharing when we transfer the results to the output buffers,
108111
// but this is the same as the unblocked case so we won't worry about it.
109-
const auto max_block_cols = sanisizer::min(num_rhs, primary_block_size);
110-
const auto max_block_rows = std::min(length, primary_block_size);
112+
const RightIndex_ max_block_cols = sanisizer::min(right_NC, options.primary_block_size);
113+
const LeftIndex_ max_block_rows = sanisizer::min(length, options.primary_block_size);
111114
tmp_output.emplace(sanisizer::product<I<decltype(tmp_output->size())> >(max_block_cols, max_block_rows));
112115
optr = tmp_output->data();
113116
} else {
114117
optr = output; // no need to add 'start' as this is zero in the single-threaded case.
115118
}
116119

117-
LeftIndex_ r = 0;
118-
while (r < length) {
119-
const LeftIndex_ rnum = std::min<LeftIndex_>(primary_block_size, length - r);
120-
for (LeftIndex_ rcounter = 0; rcounter < rnum; ++rcounter) {
121-
lptrs[rcounter] = ext->fetch(lbuffers[rcounter].data());
120+
LeftIndex_ lr = 0;
121+
while (lr < length) {
122+
const LeftIndex_ lr_num = sanisizer::min(options.primary_block_size, length - lr);
123+
for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
124+
lptrs[lr_counter] = ext->fetch(lbuffers[lr_counter].data());
122125
}
123126

124-
RightIndex_ h = 0;
125-
while (h < num_rhs) {
126-
const RightIndex_ hnum = sanisizer::min(primary_block_size, num_rhs - h);
127+
RightIndex_ rc = 0;
128+
while (rc < right_NC) {
129+
const RightIndex_ rc_num = sanisizer::min(options.primary_block_size, right_NC - rc);
127130

128-
LeftIndex_ row_offset, col_offset, stride;
131+
LeftIndex_ out_row_offset, out_stride;
132+
RightIndex_ out_col_offset;
129133
if (do_parallel) {
130-
std::fill_n(optr, sanisizer::product_unsafe<std::size_t>(hnum, rnum), 0);
131-
row_offset = 0;
132-
col_offset = 0;
133-
stride = rnum;
134+
std::fill_n(optr, sanisizer::product_unsafe<std::size_t>(rc_num, lr_num), 0);
135+
out_row_offset = 0;
136+
out_col_offset = 0;
137+
out_stride = lr_num;
134138
} else {
135-
row_offset = r; // again, no need to add start, as it should be zero if there's only one thread.
136-
col_offset = h;
137-
stride = NR;
139+
out_row_offset = lr; // again, no need to add start, as it should be zero if there's only one thread.
140+
out_col_offset = rc;
141+
out_stride = left_NR;
138142
}
139143

140-
LeftIndex_ c = 0;
141-
while (c < NC) {
142-
const LeftIndex_ cnum = std::min<LeftIndex_>(secondary_block_size, NC - c);
143-
for (RightIndex_ hcounter = 0; hcounter < hnum; ++hcounter) {
144-
const auto& rightcol = right[h + hcounter];
145-
for (LeftIndex_ rcounter = 0; rcounter < rnum; ++rcounter) {
146-
auto& dest = optr[sanisizer::nd_offset<std::size_t>(row_offset + rcounter, stride, col_offset + hcounter)];
144+
LeftIndex_ cd = 0;
145+
while (cd < common_dim) {
146+
const LeftIndex_ cd_num = sanisizer::min(options.secondary_block_size, common_dim - cd);
147+
for (RightIndex_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
148+
const auto& rightcol = right_ptrs[rc + rc_counter];
149+
for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
150+
auto& dest = optr[sanisizer::nd_offset<std::size_t>(out_row_offset + lr_counter, out_stride, out_col_offset + rc_counter)];
147151
dest = dense_dot_product<accumulators_>(
148-
cnum,
149-
rightcol + c,
150-
lptrs[rcounter] + c,
152+
cd_num, // cast to size_t is safe due to tatami's contract.
153+
rightcol + cd,
154+
lptrs[lr_counter] + cd,
151155
dest
152156
);
153157
}
154158
}
155-
c += cnum;
159+
cd += cd_num;
156160
}
157161

158162
if (do_parallel) {
159-
for (RightIndex_ hcounter = 0; hcounter < hnum; ++hcounter) {
163+
for (RightIndex_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
160164
std::copy_n(
161-
optr + sanisizer::product_unsafe<std::size_t>(rnum, hcounter),
162-
rnum,
163-
output + sanisizer::nd_offset<std::size_t>(start + r, NR, h + hcounter)
165+
optr + sanisizer::product_unsafe<std::size_t>(lr_num, rc_counter),
166+
lr_num,
167+
output + sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc + rc_counter)
164168
);
165169
}
166170
}
167171

168-
h += hnum;
172+
rc += rc_num;
169173
}
170-
r += rnum;
174+
lr += lr_num;
171175
}
172-
}, NR, options.num_threads);
176+
}, left_NR, options.num_threads);
173177
}
174178

175179
}

include/tatami_mult/dense_matrix/dense_row/column_to_row.hpp

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -59,44 +59,47 @@ struct MultiplyDenseRowWithDenseColumnMatrixToRowOutputOptions {
5959
template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
6060
void multiply_dense_row_with_dense_column_matrix_to_row_output(
6161
const tatami::Matrix<LeftValue_, LeftIndex_>& left,
62-
const std::vector<const RightValue_*>& right,
63-
const RightIndex_ num_rhs,
62+
const tatami::Matrix<RightValue_, RightIndex_>& right,
6463
Output_* const output,
6564
const MultiplyDenseRowWithDenseColumnMatrixToRowOutputOptions& options
6665
) {
67-
const auto NR = left.nrow();
68-
const auto NC = left.ncol();
66+
const auto left_NR = left.nrow();
67+
const auto common_dim = left.ncol();
68+
const auto right_NC = right.ncol();
69+
70+
auto right_buffers = tatami::create_container_of_Index_size<std::vector<std::vector<RightValue_> > >(right_NC);
71+
auto right_ptrs = tatami::create_container_of_Index_size<std::vector<const RightValue_*> >(right_NC);
72+
populate_dense_buffers(false, right_NC, common_dim, right, right_buffers, right_ptrs, options.num_threads);
6973

7074
if (options.primary_block_size == 1) {
7175
tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
7276
auto lext = tatami::consecutive_extractor<false>(left, true, start, length);
73-
auto lbuffer = tatami::create_container_of_Index_size<std::vector<LeftValue_> >(NC);
74-
for (LeftIndex_ r = 0; r < length; ++r) {
77+
auto lbuffer = tatami::create_container_of_Index_size<std::vector<LeftValue_> >(common_dim);
78+
for (LeftIndex_ lr = 0; lr < length; ++lr) {
7579
const auto lptr = lext->fetch(lbuffer.data());
76-
for (RightIndex_ h = 0; h < num_rhs; ++h) {
77-
output[sanisizer::nd_offset<std::size_t>(h, num_rhs, start + r)] = dense_dot_product<accumulators_>(NC, lptr, right[h], static_cast<Output_>(0));
80+
for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
81+
// cast of common_dim to size_t is safe due to tatami's contract.
82+
const auto res = dense_dot_product<accumulators_>(common_dim, lptr, right_ptrs[rc], static_cast<Output_>(0));
83+
output[sanisizer::nd_offset<std::size_t>(rc, right_NC, start + lr)] = res;
7884
}
7985
}
80-
}, NR, options.num_threads);
86+
}, left_NR, options.num_threads);
8187
return;
8288
}
8389

84-
const auto primary_block_size = sanisizer::cast<LeftIndex_>(options.primary_block_size);
85-
const auto secondary_block_size = sanisizer::cast<LeftIndex_>(options.secondary_block_size);
86-
8790
const bool do_parallel = options.num_threads > 1;
8891
if (!do_parallel) {
89-
std::fill_n(output, sanisizer::product_unsafe<std::size_t>(NR, num_rhs), 0);
92+
std::fill_n(output, sanisizer::product_unsafe<std::size_t>(left_NR, right_NC), 0);
9093
}
9194

9295
tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
9396
auto ext = tatami::consecutive_extractor<false>(left, true, start, length);
9497

95-
const LeftIndex_ max_block_rows = std::min(length, primary_block_size);
98+
const LeftIndex_ max_block_rows = sanisizer::min(length, options.primary_block_size);
9699
std::vector<std::vector<LeftValue_> > lbuffers;
97100
lbuffers.reserve(max_block_rows);
98101
for (LeftIndex_ b = 0; b < max_block_rows; ++b) {
99-
lbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(NC));
102+
lbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
100103
}
101104
auto lptrs = tatami::create_container_of_Index_size<std::vector<const LeftValue_*> >(max_block_rows);
102105

@@ -107,70 +110,71 @@ void multiply_dense_row_with_dense_column_matrix_to_row_output(
107110
// This aims to mitigate false sharing as we update each block's partial dot products in the loop over the common dimension.
108111
// There is still some potential for false sharing when we transfer the results to the output buffers,
109112
// but this is the same as the unblocked case so we won't worry about it.
110-
const auto max_block_cols = sanisizer::min(num_rhs, primary_block_size);
111-
const auto max_block_rows = std::min(length, primary_block_size);
113+
const auto max_block_cols = sanisizer::min(right_NC, options.primary_block_size);
114+
const auto max_block_rows = sanisizer::min(length, options.primary_block_size);
112115
tmp_output.emplace(sanisizer::product<I<decltype(tmp_output->size())> >(max_block_cols, max_block_rows));
113116
optr = tmp_output->data();
114117
} else {
115118
optr = output; // no need to add 'start' as this is zero in the single-threaded case.
116119
}
117120

118-
LeftIndex_ r = 0;
119-
while (r < length) {
120-
const LeftIndex_ rnum = std::min<LeftIndex_>(primary_block_size, length - r);
121-
for (LeftIndex_ rcounter = 0; rcounter < rnum; ++rcounter) {
122-
lptrs[rcounter] = ext->fetch(lbuffers[rcounter].data());
121+
LeftIndex_ lr = 0;
122+
while (lr < length) {
123+
const LeftIndex_ lr_num = sanisizer::min(options.primary_block_size, length - lr);
124+
for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
125+
lptrs[lr_counter] = ext->fetch(lbuffers[lr_counter].data());
123126
}
124127

125-
RightIndex_ h = 0;
126-
while (h < num_rhs) {
127-
const RightIndex_ hnum = sanisizer::min(primary_block_size, num_rhs - h);
128+
RightIndex_ rc = 0;
129+
while (rc < right_NC) {
130+
const RightIndex_ rc_num = sanisizer::min(options.primary_block_size, right_NC - rc);
128131

129-
RightIndex_ row_offset, col_offset, stride;
132+
LeftIndex_ out_row_offset;
133+
RightIndex_ out_col_offset, out_stride;
130134
if (do_parallel) {
131-
std::fill_n(optr, sanisizer::product_unsafe<std::size_t>(hnum, rnum), 0);
132-
row_offset = 0;
133-
col_offset = 0;
134-
stride = hnum;
135+
std::fill_n(optr, sanisizer::product_unsafe<std::size_t>(rc_num, lr_num), 0);
136+
out_row_offset = 0;
137+
out_col_offset = 0;
138+
out_stride = rc_num;
135139
} else {
136-
row_offset = r; // start == 0 in serial code, so no need to add start here.
137-
col_offset = h;
138-
stride = num_rhs;
140+
out_row_offset = lr; // start == 0 in serial code, so no need to add start here.
141+
out_col_offset = rc;
142+
out_stride = right_NC;
139143
}
140144

141-
LeftIndex_ c = 0;
142-
while (c < NC) {
143-
const LeftIndex_ cnum = std::min<LeftIndex_>(secondary_block_size, NC - c);
144-
for (LeftIndex_ rcounter = 0; rcounter < rnum; ++rcounter) {
145-
const auto leftrow = lptrs[rcounter];
146-
for (RightIndex_ hcounter = 0; hcounter < hnum; ++hcounter) {
147-
auto& dest = optr[sanisizer::nd_offset<std::size_t>(col_offset + hcounter, stride, row_offset + rcounter)];
145+
LeftIndex_ cd = 0;
146+
while (cd < common_dim) {
147+
const LeftIndex_ cd_num = sanisizer::min(options.secondary_block_size, common_dim - cd);
148+
for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
149+
const auto leftrow = lptrs[lr_counter];
150+
for (RightIndex_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
151+
auto& dest = optr[sanisizer::nd_offset<std::size_t>(out_col_offset + rc_counter, out_stride, out_row_offset + lr_counter)];
148152
dest = dense_dot_product<accumulators_>(
149-
cnum,
150-
right[h + hcounter] + c,
151-
leftrow + c,
153+
cd_num, // cast to size_t is safe due to tatami's contract.
154+
right_ptrs[rc + rc_counter] + cd,
155+
leftrow + cd,
152156
dest
153157
);
154158
}
155159
}
156-
c += cnum;
160+
cd += cd_num;
157161
}
158162

159163
if (do_parallel) {
160-
for (LeftIndex_ rcounter = 0; rcounter < rnum; ++rcounter) {
164+
for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
161165
std::copy_n(
162-
optr + sanisizer::product_unsafe<std::size_t>(hnum, rcounter),
163-
hnum,
164-
output + sanisizer::nd_offset<std::size_t>(h, num_rhs, start + r + rcounter)
166+
optr + sanisizer::product_unsafe<std::size_t>(rc_num, lr_counter),
167+
rc_num,
168+
output + sanisizer::nd_offset<std::size_t>(rc, right_NC, start + lr + lr_counter)
165169
);
166170
}
167171
}
168172

169-
h += hnum;
173+
rc += rc_num;
170174
}
171-
r += rnum;
175+
lr += lr_num;
172176
}
173-
}, NR, options.num_threads);
177+
}, left_NR, options.num_threads);
174178
}
175179

176180
}

0 commit comments

Comments
 (0)