tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
column_to_column.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_DENSE_MATRIX_DENSE_ROW_COLUMN_TO_COLUMN_HPP
2#define TATAMI_MULT_DENSE_MATRIX_DENSE_ROW_COLUMN_TO_COLUMN_HPP
3
4#include <vector>
5#include <cstddef>
6
7#include "tatami/tatami.hpp"
8#include "sanisizer/sanisizer.hpp"
9
10#include "../utils.hpp"
11#include "../../utils.hpp"
12
18namespace tatami_mult {
19
20/* See https://github.com/tatami-inc/test-multiplication/tree/master/dense_row/dense_matrix
21 * for an explanation of the choice of algorithm.
22 */
23
48
68template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightColumns_, class GetRightColumn_, typename Output_>
71 const RightColumns_ right_columns,
72 GetRightColumn_ get_right_column,
73 Output_* const output,
75) {
76 const auto left_NR = left.nrow();
77 const auto common_dim = left.ncol();
78
79 if (options.primary_block_size == 1) {
80 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
81 auto lext = tatami::consecutive_extractor<false>(left, true, start, length);
83 for (LeftIndex_ lr = 0; lr < length; ++lr) {
84 const auto lptr = lext->fetch(lbuffer.data());
85 for (RightColumns_ rc = 0; rc < right_columns; ++rc) {
86 output[sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc)] = dense_dot_product<accumulators_>(
87 common_dim, // cast of common_dim to size_t is safe due to tatami's contract.
88 lptr,
89 get_right_column(rc),
90 static_cast<Output_>(0)
91 );
92 }
93 }
94 }, left_NR, options.num_threads);
95 return;
96 }
97
98 const bool do_parallel = options.num_threads > 1;
99 if (!do_parallel) {
100 // Product must fit in a size_t in order for output to have been allocated correctly in the first place.
101 // Technically, right_columns could be larger than a size_t if left_NR == 0, but the product after wraparound would still be zero, so it's fine.
102 std::fill_n(output, sanisizer::product_unsafe<std::size_t>(left_NR, right_columns), 0);
103 }
104
105 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
106 auto ext = tatami::consecutive_extractor<false>(left, true, start, length);
107
108 const LeftIndex_ max_block_rows = sanisizer::min(length, options.primary_block_size);
109 std::vector<std::vector<LeftValue_> > lbuffers;
110 lbuffers.reserve(max_block_rows);
111 for (LeftIndex_ b = 0; b < max_block_rows; ++b) {
112 lbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
113 }
115
116 std::optional<std::vector<Output_> > tmp_output;
117 Output_* optr;
118 if (do_parallel) {
119 // For the multi-threaded case, we create some temporary buffers to hold the partial dot products for the current set of submatrices.
120 // This aims to mitigate false sharing as we update each block's partial dot products in the loop over the common dimension.
121 // There is still some potential for false sharing when we transfer the results to the output buffers,
122 // but this is the same as the unblocked case so we won't worry about it.
123 const RightColumns_ max_block_cols = sanisizer::min(right_columns, options.primary_block_size);
124 const LeftIndex_ max_block_rows = sanisizer::min(length, options.primary_block_size);
125 tmp_output.emplace(sanisizer::product<I<decltype(tmp_output->size())> >(max_block_cols, max_block_rows));
126 optr = tmp_output->data();
127 } else {
128 optr = output; // no need to add 'start' as this is zero in the single-threaded case.
129 }
130
131 LeftIndex_ lr = 0;
132 while (lr < length) {
133 const LeftIndex_ lr_num = sanisizer::min(options.primary_block_size, length - lr);
134 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
135 lptrs[lr_counter] = ext->fetch(lbuffers[lr_counter].data());
136 }
137
138 RightColumns_ rc = 0;
139 while (rc < right_columns) {
140 const RightColumns_ rc_num = sanisizer::min(options.primary_block_size, right_columns - rc);
141
142 LeftIndex_ out_row_offset, out_stride;
143 RightColumns_ out_col_offset;
144 if (do_parallel) {
145 std::fill_n(optr, sanisizer::product_unsafe<std::size_t>(rc_num, lr_num), 0);
146 out_row_offset = 0;
147 out_col_offset = 0;
148 out_stride = lr_num;
149 } else {
150 out_row_offset = lr; // again, no need to add 'start' as this should be zero if there's only one thread.
151 out_col_offset = rc;
152 out_stride = left_NR;
153 }
154
155 LeftIndex_ cd = 0;
156 while (cd < common_dim) {
157 const LeftIndex_ cd_num = sanisizer::min(options.secondary_block_size, common_dim - cd);
158 for (RightColumns_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
159 const auto& rightcol = get_right_column(rc + rc_counter);
160 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
161 auto& dest = optr[sanisizer::nd_offset<std::size_t>(out_row_offset + lr_counter, out_stride, out_col_offset + rc_counter)];
162 dest = dense_dot_product<accumulators_>(
163 cd_num, // cast to size_t is safe due to tatami's contract.
164 rightcol + cd,
165 lptrs[lr_counter] + cd,
166 dest
167 );
168 }
169 }
170 cd += cd_num;
171 }
172
173 if (do_parallel) {
174 for (RightColumns_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
175 std::copy_n(
176 optr + sanisizer::product_unsafe<std::size_t>(lr_num, rc_counter),
177 lr_num,
178 output + sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc + rc_counter)
179 );
180 }
181 }
182
183 rc += rc_num;
184 }
185 lr += lr_num;
186 }
187 }, left_NR, options.num_threads);
188}
189
212template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
216 Output_* const output,
218) {
219 const auto right_NC = right.ncol();
222 const auto common_dim = left.ncol();
223 populate_dense_buffers(false, right_NC, common_dim, right, right_buffers, right_ptrs, options.num_threads);
224
226 left,
227 right_NC,
228 [&](const RightIndex_ rc) -> const RightValue_* {
229 return right_ptrs[rc];
230 },
231 output,
232 options
233 );
234}
235
236}
237
238#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
Multiplication of tatami matrices.
Definition column_to_column.hpp:19
void multiply_dense_row_with_dense_column_matrix_to_column_output(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const RightColumns_ right_columns, GetRightColumn_ get_right_column, Output_ *const output, const MultiplyDenseRowWithDenseColumnMatrixToColumnOutputOptions &options)
Definition column_to_column.hpp:69
int parallelize(Function_ fun, const Index_ tasks, const int workers)
I< decltype(std::declval< Container_ >().size())> cast_Index_to_container_size(const Index_ x)
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
auto consecutive_extractor(const Matrix< Value_, Index_ > &matrix, const bool row, const Index_ iter_start, const Index_ iter_length, Args_ &&... args)
Options for multiply_dense_row_with_dense_column_matrix_to_column_output().
Definition column_to_column.hpp:27