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