tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
dense_row.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_MULTIPLE_VECTORS_DENSE_ROW_HPP
2#define TATAMI_MULT_MULTIPLE_VECTORS_DENSE_ROW_HPP
3
4#include <cstddef>
5#include <vector>
6#include <type_traits>
7
8#include "tatami/tatami.hpp"
9
10#include "../utils.hpp"
11#include "../dense_dot_product.hpp"
12
18namespace tatami_mult {
19
20/* See https://github.com/tatami-inc/test-multiplication/tree/master/dense_row/multiple_vectors
21 * for an explanation of the choice of algorithm.
22 */
23
48
69template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightVectors_, typename GetRightVector_, typename GetOutputVector_>
72 const RightVectors_ right_vectors,
73 GetRightVector_ get_right_vector,
74 GetOutputVector_ get_output_vector,
76) {
77 const auto left_NR = left.nrow();
78 const auto common_dim = left.ncol();
79 typedef I<decltype(get_output_vector(0)[0])> Output;
80
81 if (options.primary_block_size == 1) {
82 tatami::parallelize([&](int, const LeftIndex_ start, const LeftIndex_ length) -> void {
83 auto lext = tatami::consecutive_extractor<false>(left, true, start, length);
85 for (LeftIndex_ lr = 0; lr < length; ++lr) {
86 const auto lptr = lext->fetch(lbuffer.data());
87 for (RightVectors_ rv = 0; rv < right_vectors; ++rv) {
88 get_output_vector(rv)[start + lr] = dense_dot_product<accumulators_>(
89 common_dim, // Implicit cast to std::size_t is safe, as per the tatami contract.
90 lptr,
91 get_right_vector(rv),
92 static_cast<Output>(0)
93 );
94 }
95 }
96 }, left_NR, options.num_threads);
97 return;
98 }
99
100 const bool do_parallel = options.num_threads > 1;
101 tatami::parallelize([&](int, const LeftIndex_ start, const LeftIndex_ length) -> void {
102 auto ext = tatami::consecutive_extractor<false>(left, true, start, length);
103
104 const LeftIndex_ max_block_rows = sanisizer::min(length, options.primary_block_size);
105 std::vector<std::vector<LeftValue_> > left_buffers;
106 left_buffers.reserve(max_block_rows);
107 for (LeftIndex_ lr = 0; lr < max_block_rows; ++lr) {
108 left_buffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
109 }
111
112 if (!do_parallel) {
113 // Zeroing all of the buffers if we're operating on a single thread,
114 // as we're computing partial dot products and we need to start from zero.
115 for (RightVectors_ rc = 0; rc < right_vectors; ++rc) {
116 std::fill_n(get_output_vector(rc), length, 0);
117 }
118
119 LeftIndex_ lr = 0;
120 while (lr < length) {
121 const LeftIndex_ lr_num = sanisizer::min(options.primary_block_size, length - lr);
122 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
123 left_ptrs[lr_counter] = ext->fetch(left_buffers[lr_counter].data());
124 }
125
126 RightVectors_ rc = 0;
127 while (rc < right_vectors) {
128 const RightVectors_ rc_num = sanisizer::min(options.primary_block_size, right_vectors - rc);
129
130 LeftIndex_ cd = 0;
131 while (cd < common_dim) {
132 const LeftIndex_ cd_num = sanisizer::min(options.secondary_block_size, common_dim - cd);
133 for (RightVectors_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
134 const auto outvec = get_output_vector(rc + rc_counter) + start + lr;
135 const auto rightvec = get_right_vector(rc + rc_counter);
136
137 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
138 auto& dest = outvec[lr_counter];
139 dest = dense_dot_product<accumulators_>(
140 cd_num, // Implicit cast to std::size_t is safe, as per the tatami contract.
141 rightvec + cd,
142 left_ptrs[lr_counter] + cd,
143 dest
144 );
145 }
146 }
147 cd += cd_num;
148 }
149
150 rc += rc_num;
151 }
152 lr += lr_num;
153 }
154
155 } else {
156 // For the multi-threaded case, we create some temporary buffers to hold the partial dot products for the current set of submatrices.
157 // This aims to mitigate false sharing as we update each block's partial dot products in the loop over the common dimension.
158 // There is still some potential for false sharing when we transfer the results to the output buffers,
159 // but this is the same as the unblocked case so we won't worry about it.
160 std::vector<std::vector<Output> > tmp_output;
161 {
162 const RightVectors_ max_block_cols = sanisizer::min(right_vectors, options.primary_block_size);
163 tmp_output.reserve(max_block_cols);
164 for (RightVectors_ rc = 0; rc < max_block_cols; ++rc) {
165 tmp_output.emplace_back(tatami::cast_Index_to_container_size<std::vector<Output> >(max_block_rows));
166 }
167 }
168
169 LeftIndex_ lr = 0;
170 while (lr < length) {
171 const LeftIndex_ lr_num = sanisizer::min(options.primary_block_size, length - lr);
172 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
173 left_ptrs[lr_counter] = ext->fetch(left_buffers[lr_counter].data());
174 }
175
176 RightVectors_ rc = 0;
177 while (rc < right_vectors) {
178 const RightVectors_ rc_num = sanisizer::min(options.primary_block_size, right_vectors - rc);
179
180 LeftIndex_ cd = 0;
181 while (cd < common_dim) {
182 const LeftIndex_ cd_num = sanisizer::min(options.secondary_block_size, common_dim - cd);
183 for (RightVectors_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
184 const auto outvec = tmp_output[rc_counter].data();
185 const auto rightvec = get_right_vector(rc + rc_counter);
186
187 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
188 auto& dest = outvec[lr_counter];
189 dest = dense_dot_product<accumulators_>(
190 cd_num, // Implicit cast to std::size_t is safe, as per the tatami contract.
191 rightvec + cd,
192 left_ptrs[lr_counter] + cd,
193 dest
194 );
195 }
196 }
197 cd += cd_num;
198 }
199
200 for (RightVectors_ rc_counter = 0; rc_counter < rc_num; ++rc_counter) {
201 auto& src = tmp_output[rc_counter];
202 std::copy_n(src.begin(), lr_num, get_output_vector(rc + rc_counter) + start + lr);
203 std::fill_n(src.begin(), lr_num, 0);
204 }
205
206 rc += rc_num;
207 }
208 lr += lr_num;
209 }
210 }
211 }, left_NR, options.num_threads);
212}
213
233template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightValue_, typename Output_>
236 const std::vector<RightValue_*>& right,
237 const std::vector<Output_*>& output,
239) {
240 const auto right_vectors = right.size();
241 typedef I<decltype(right_vectors)> RightVectors;
243 left,
244 right_vectors,
245 [&](const RightVectors rc) -> const RightValue_* {
246 return right[rc];
247 },
248 [&](const RightVectors rc) -> Output_* {
249 return output[rc];
250 },
251 options
252 );
253}
254
255}
256
257#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_multiple_vectors(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const RightVectors_ right_vectors, GetRightVector_ get_right_vector, GetOutputVector_ get_output_vector, const MultiplyDenseRowWithMultipleVectorsOptions &options)
Definition dense_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_multiple_vectors().
Definition dense_row.hpp:27