tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
row_to_column.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_SPARSE_MATRIX_DENSE_ROW_ROW_TO_COLUMN_HPP
2#define TATAMI_MULT_SPARSE_MATRIX_DENSE_ROW_ROW_TO_COLUMN_HPP
3
4#include <cstddef>
5#include <vector>
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/sparse_matrix
21 * for an explanation of the choice of algorithm.
22 */
23
44
64template<typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
68 Output_* const output,
70) {
71 const auto left_NR = left.nrow();
72 const auto common_dim = left.ncol();
73 const auto right_NC = right.ncol();
74
78 populate_sparse_buffers(true, common_dim, right_NC, right, right_vbuffers, right_ibuffers, right_ranges, options.num_threads);
79
80 // We'll be skipping the empty RHS rows during iteration.
81 auto right_non_empty = filter_non_empty_sparse(
82 right_ranges,
83 [&](RightIndex_) -> void {}
84 );
85
86 if (options.block_size == 1) {
87 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
88 auto ext = tatami::consecutive_extractor<false>(left, true, start, length);
90
91 // Use a temporary buffer to (i) improve data locality and (ii) avoid false sharing.
93
94 for (LeftIndex_ lr = 0; lr < length; ++lr) {
95 const auto lptr = ext->fetch(dbuffer.data());
96
97 auto loop_body = [&](LeftIndex_ cd) -> void {
98 const auto rrange = right_ranges[cd];
99 const Output_ mult = lptr[cd];
100 for (RightIndex_ x = 0; x < rrange.number; ++x) {
101 tmp_row[rrange.index[x]] += mult * static_cast<Output_>(rrange.value[x]);
102 }
103 };
104
105 if (right_non_empty.has_value()) {
106 for (const auto cd : *right_non_empty) {
107 loop_body(cd);
108 }
109 } else {
110 for (LeftIndex_ cd = 0; cd < common_dim; ++cd) {
111 loop_body(cd);
112 }
113 }
114
115 // Technically, we could limit the transposition to only those RHS columns that are not empty.
116 // This avoids unnecessary accesses to non-contiguous memory that will always be zero.
117 // To implement this, we'd need to do another pass through the RHS matrix to find the empty columns and store their IDs.
118 // Such extra complexity is probably not worth it; we're not in the hot loop,
119 // and accessing non-consecutive IDs may be a pessimization if it prevents compiler optimizations and/or strided prefetching.
120 // Ensuring correct zeroing of the output columns also becomes a bit complicated if we skip empty RHS columns.
121 for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
122 output[sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc)] = tmp_row[rc];
123 }
124 std::fill(tmp_row.begin(), tmp_row.end(), 0);
125 }
126 }, left_NR, options.num_threads);
127
128 } else {
129 const bool do_parallel = options.num_threads > 1;
130 if (!do_parallel) {
131 std::fill_n(output, sanisizer::product_unsafe<std::size_t>(left_NR, right_NC), 0);
132 }
133
134 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
135 auto ext = tatami::consecutive_extractor<false>(left, true, start, length);
136
137 const LeftIndex_ max_block_rows = sanisizer::min(length, options.block_size);
138 std::vector<std::vector<LeftValue_> > lbuffers;
139 lbuffers.reserve(max_block_rows);
140 for (LeftIndex_ b = 0; b < max_block_rows; ++b) {
141 lbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
142 }
145
146 // Create a temporary buffer to minimize false sharing during updates across all 'cd'.
147 std::optional<std::vector<Output_> > tmp_cols;
148 if (do_parallel) {
149 tmp_cols.emplace(sanisizer::product<I<decltype(tmp_cols->size())> >(max_block_rows, right_NC));
150 }
151
152 LeftIndex_ lr = 0;
153 while (lr < length) {
154 const LeftIndex_ lr_num = sanisizer::min(options.block_size, length - lr);
155 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
156 lptrs[lr_counter] = ext->fetch(lbuffers[lr_counter].data());
157 }
158
159 Output_* tmp_optr;
160 LeftIndex_ out_row_offset;
161 LeftIndex_ out_stride;
162 if (do_parallel) {
163 tmp_optr = tmp_cols->data();
164 out_row_offset = 0;
165 out_stride = lr_num;
166 } else {
167 tmp_optr = output;
168 out_row_offset = start + lr;
169 out_stride = left_NR;
170 }
171
172 auto loop_body = [&](LeftIndex_ cd) -> void {
173 const auto rrange = right_ranges[cd];
174
175 // Transfer this block of the 'cd'-th LHS column into a single buffer for a faster vector multiply-add in the inner loop.
176 // This rationale is unique to this function and is unlike any of the explanations in the sparse-blocking documentation.
177 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
178 colbuffer[lr_counter] = lptrs[lr_counter][cd];
179 }
180
181 for (RightIndex_ x = 0; x < rrange.number; ++x) {
182 const Output_ mult = rrange.value[x];
183 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
184 tmp_optr[sanisizer::nd_offset<std::size_t>(out_row_offset + lr_counter, out_stride, rrange.index[x])] += mult * static_cast<Output_>(colbuffer[lr_counter]);
185 }
186 }
187 };
188
189 if (right_non_empty.has_value()) {
190 for (const auto cd : *right_non_empty) {
191 loop_body(cd);
192 }
193 } else {
194 for (LeftIndex_ cd = 0; cd < common_dim; ++cd) {
195 loop_body(cd);
196 }
197 }
198
199 if (do_parallel) {
200 for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
201 const auto src = tmp_cols->data() + sanisizer::product_unsafe<std::size_t>(rc, lr_num);
202 std::copy_n(src, lr_num, output + sanisizer::nd_offset<std::size_t>(start + lr, left_NR, rc));
203 }
204 std::fill_n(tmp_cols->data(), sanisizer::product_unsafe<std::size_t>(right_NC, lr_num), 0);
205 }
206
207 lr += lr_num;
208 }
209 }, left_NR, options.num_threads);
210 }
211}
212
213}
214
215#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_sparse_row_matrix_to_column_output(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const tatami::Matrix< RightValue_, RightIndex_ > &right, Output_ *const output, const MultiplyDenseRowWithSparseRowMatrixToColumnOutputOptions &options)
Definition row_to_column.hpp:65
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_sparse_row_matrix_to_column_output().
Definition row_to_column.hpp:27