tatami_mult
Multiply tatami matrices
Loading...
Searching...
No Matches
column_to_row.hpp
Go to the documentation of this file.
1#ifndef TATAMI_MULT_SPARSE_MATRIX_SPARSE_ROW_COLUMN_TO_ROW_HPP
2#define TATAMI_MULT_SPARSE_MATRIX_SPARSE_ROW_COLUMN_TO_ROW_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#include "../../sparse_dot_product.hpp"
13
19namespace tatami_mult {
20
21/* See https://github.com/tatami-inc/test-multiplication/tree/master/sparse_row/sparse_matrix
22 * for an explanation of the choice of algorithm.
23 */
24
43
65template<std::size_t accumulators_ = 4, typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
69 Output_* const output,
71) {
72 const auto left_NR = left.nrow();
73 const auto common_dim = left.ncol();
74 const auto right_NC = right.ncol();
75
79 populate_sparse_buffers(false, right_NC, common_dim, right, right_vbuffers, right_ibuffers, right_ranges, options.num_threads);
80
81 if (options.block_size == 1) {
82 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
83 auto ext = tatami::consecutive_extractor<true>(left, true, start, length);
86
87 // We expand each sparse vector into a dense buffer for easy look-up in sparse_dot_product().
89
90 for (LeftIndex_ lr = 0; lr < length; ++lr) {
91 const auto lrange = ext->fetch(vbuffer.data(), ibuffer.data());
92 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
93 expanded[lrange.index[x]] = lrange.value[x];
94 }
95
96 // No point looping over the non-empty RHS columns, as we still need to zero the output columns corresponding to empty RHS columns.
97 // So, we might as well handle the zeroing in the same loop and save ourselves the trouble.
98 for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
99 const auto rrange = right_ranges[rc];
100
101 // Some false sharing potential here, but we just touch each location once per outer loop, so it's fine.
102 output[sanisizer::nd_offset<std::size_t>(rc, right_NC, start + lr)] = sparse_dot_product<accumulators_>(
103 rrange.number, // Implicit cast to size_t is safe, as per the tatami contract.
104 rrange.value,
105 rrange.index,
106 expanded.data(),
107 static_cast<Output_>(0)
108 );
109 }
110
111 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
112 expanded[lrange.index[x]] = 0;
113 }
114 }
115 }, left_NR, options.num_threads);
116
117 } else {
118 tatami::parallelize([&](int, LeftIndex_ start, LeftIndex_ length) -> void {
119 auto ext = tatami::consecutive_extractor<true>(left, true, start, length);
120
121 const LeftIndex_ max_block_rows = sanisizer::min(length, options.block_size);
122 std::vector<std::vector<LeftValue_> > left_vbuffers;
123 std::vector<std::vector<LeftIndex_> > left_ibuffers;
124 std::vector<std::vector<LeftValue_> > expanded;
125 left_vbuffers.reserve(max_block_rows);
126 left_ibuffers.reserve(max_block_rows);
127 expanded.reserve(max_block_rows);
128 for (LeftIndex_ b = 0; b < max_block_rows; ++b) {
129 left_vbuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
130 left_ibuffers.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftIndex_> >(common_dim));
131 expanded.emplace_back(tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim));
132 }
134 std::vector<LeftIndex_> left_non_empty;
135 left_non_empty.reserve(max_block_rows);
136
137 LeftIndex_ lr = 0;
138 while (lr < length) {
139 // We only consider the LHS rows with at least one structural non-zero.
140 // Thus, our block consists of 'options.block_size' non-empty LHS rows, rather than fixed row-wise chunks of the LHS matrix.
141 // This ensures that we don't waste iterations on LHS rows that will only have zeros in the output matrix (and are filled as such).
142 left_non_empty.clear();
143 LeftIndex_ lr_num = 0;
144 bool all_non_empty = true;
145 auto new_lr = lr;
146
147 do {
148 auto lrange = ext->fetch(left_vbuffers[lr_num].data(), left_ibuffers[lr_num].data());
149 if (lrange.number == 0) {
150 std::fill_n(output + sanisizer::product_unsafe<std::size_t>(start + new_lr, right_NC), right_NC, 0);
151 all_non_empty = false;
152 ++new_lr;
153 continue;
154 }
155
156 left_ranges[lr_num] = std::move(lrange);
157 auto& curex = expanded[lr_num];
158 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
159 curex[lrange.index[x]] = lrange.value[x];
160 }
161 left_non_empty.push_back(new_lr);
162 ++lr_num;
163 ++new_lr;
164
165 if (sanisizer::is_equal(lr_num, options.block_size)) {
166 break;
167 }
168 } while (new_lr < length);
169
170 auto task = [&](auto converter) -> void {
171 // Deliberately iterating over the sparse RHS columns in the outer loop and the dense (expanded) LHS rows in the inner loop.
172 // This aims to keep the entirety of the dense LHS block in cache across multiple RHS columns, provided common_dim is small.
173 // If we did it the other way around, it would just be the same as the block_size == 1 case, but with more looping overhead.
174 for (RightIndex_ rc = 0; rc < right_NC; ++rc) {
175 const auto rrange = right_ranges[rc];
176 if (rrange.number == 0) {
177 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
178 output[sanisizer::nd_offset<std::size_t>(rc, right_NC, converter(lr_counter))] = 0;
179 }
180 continue;
181 }
182
183 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
184 // Some false sharing potential here, but we just touch each location once per outer loop, so it's fine.
185 output[sanisizer::nd_offset<std::size_t>(rc, right_NC, converter(lr_counter))] = sparse_dot_product<accumulators_>(
186 rrange.number, // Implicit cast to size_t is safe, as per the tatami contract.
187 rrange.value,
188 rrange.index,
189 expanded[lr_counter].data(),
190 static_cast<Output_>(0)
191 );
192 }
193 }
194 };
195
196 if (all_non_empty) {
197 task([&](const LeftIndex_ lr_counter) -> LeftIndex_ { return start + lr + lr_counter; });
198 } else {
199 for (auto& lrne : left_non_empty) {
200 lrne += start;
201 }
202 task([&](const LeftIndex_ lr_counter) -> LeftIndex_ { return left_non_empty[lr_counter]; });
203 }
204
205 for (LeftIndex_ lr_counter = 0; lr_counter < lr_num; ++lr_counter) {
206 const auto& lrange = left_ranges[lr_counter];
207 auto& curex = expanded[lr_counter];
208 for (LeftIndex_ x = 0; x < lrange.number; ++x) {
209 curex[lrange.index[x]] = 0;
210 }
211 }
212
213 lr = new_lr;
214 }
215 }, left_NR, options.num_threads);
216 }
217}
218
219}
220
221#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
Multiplication of tatami matrices.
Definition column_to_column.hpp:19
void multiply_sparse_row_with_sparse_column_matrix_to_row_output(const tatami::Matrix< LeftValue_, LeftIndex_ > &left, const tatami::Matrix< RightValue_, RightIndex_ > &right, Output_ *const output, const MultiplySparseRowWithSparseColumnMatrixToRowOutputOptions &options)
Definition column_to_row.hpp:66
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_sparse_row_with_sparse_column_matrix_to_row_output().
Definition column_to_row.hpp:28