@@ -59,44 +59,47 @@ struct MultiplyDenseRowWithDenseColumnMatrixToRowOutputOptions {
5959template <std::size_t accumulators_ = 4 , typename LeftValue_, typename LeftIndex_, typename RightValue_, typename RightIndex_, typename Output_>
6060void multiply_dense_row_with_dense_column_matrix_to_row_output (
6161 const tatami::Matrix<LeftValue_, LeftIndex_>& left,
62- const std::vector<const RightValue_*>& right,
63- const RightIndex_ num_rhs,
62+ const tatami::Matrix<RightValue_, RightIndex_>& right,
6463 Output_* const output,
6564 const MultiplyDenseRowWithDenseColumnMatrixToRowOutputOptions& options
6665) {
67- const auto NR = left.nrow ();
68- const auto NC = left.ncol ();
66+ const auto left_NR = left.nrow ();
67+ const auto common_dim = left.ncol ();
68+ const auto right_NC = right.ncol ();
69+
70+ auto right_buffers = tatami::create_container_of_Index_size<std::vector<std::vector<RightValue_> > >(right_NC);
71+ auto right_ptrs = tatami::create_container_of_Index_size<std::vector<const RightValue_*> >(right_NC);
72+ populate_dense_buffers (false , right_NC, common_dim, right, right_buffers, right_ptrs, options.num_threads );
6973
7074 if (options.primary_block_size == 1 ) {
7175 tatami::parallelize ([&](int , LeftIndex_ start, LeftIndex_ length) -> void {
7276 auto lext = tatami::consecutive_extractor<false >(left, true , start, length);
73- auto lbuffer = tatami::create_container_of_Index_size<std::vector<LeftValue_> >(NC );
74- for (LeftIndex_ r = 0 ; r < length; ++r ) {
77+ auto lbuffer = tatami::create_container_of_Index_size<std::vector<LeftValue_> >(common_dim );
78+ for (LeftIndex_ lr = 0 ; lr < length; ++lr ) {
7579 const auto lptr = lext->fetch (lbuffer.data ());
76- for (RightIndex_ h = 0 ; h < num_rhs; ++h) {
77- output[sanisizer::nd_offset<std::size_t >(h, num_rhs, start + r)] = dense_dot_product<accumulators_>(NC , lptr, right[h], static_cast <Output_>(0 ));
80+ for (RightIndex_ rc = 0 ; rc < right_NC; ++rc) {
81+ // cast of common_dim to size_t is safe due to tatami's contract.
82+ const auto res = dense_dot_product<accumulators_>(common_dim, lptr, right_ptrs[rc], static_cast <Output_>(0 ));
83+ output[sanisizer::nd_offset<std::size_t >(rc, right_NC, start + lr)] = res;
7884 }
7985 }
80- }, NR , options.num_threads );
86+ }, left_NR , options.num_threads );
8187 return ;
8288 }
8389
84- const auto primary_block_size = sanisizer::cast<LeftIndex_>(options.primary_block_size );
85- const auto secondary_block_size = sanisizer::cast<LeftIndex_>(options.secondary_block_size );
86-
8790 const bool do_parallel = options.num_threads > 1 ;
8891 if (!do_parallel) {
89- std::fill_n (output, sanisizer::product_unsafe<std::size_t >(NR , num_rhs ), 0 );
92+ std::fill_n (output, sanisizer::product_unsafe<std::size_t >(left_NR, right_NC ), 0 );
9093 }
9194
9295 tatami::parallelize ([&](int , LeftIndex_ start, LeftIndex_ length) -> void {
9396 auto ext = tatami::consecutive_extractor<false >(left, true , start, length);
9497
95- const LeftIndex_ max_block_rows = std ::min (length, primary_block_size);
98+ const LeftIndex_ max_block_rows = sanisizer ::min (length, options. primary_block_size );
9699 std::vector<std::vector<LeftValue_> > lbuffers;
97100 lbuffers.reserve (max_block_rows);
98101 for (LeftIndex_ b = 0 ; b < max_block_rows; ++b) {
99- lbuffers.emplace_back (tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(NC ));
102+ lbuffers.emplace_back (tatami::cast_Index_to_container_size<std::vector<LeftValue_> >(common_dim ));
100103 }
101104 auto lptrs = tatami::create_container_of_Index_size<std::vector<const LeftValue_*> >(max_block_rows);
102105
@@ -107,70 +110,71 @@ void multiply_dense_row_with_dense_column_matrix_to_row_output(
107110 // This aims to mitigate false sharing as we update each block's partial dot products in the loop over the common dimension.
108111 // There is still some potential for false sharing when we transfer the results to the output buffers,
109112 // but this is the same as the unblocked case so we won't worry about it.
110- const auto max_block_cols = sanisizer::min (num_rhs, primary_block_size);
111- const auto max_block_rows = std ::min (length, primary_block_size);
113+ const auto max_block_cols = sanisizer::min (right_NC, options. primary_block_size );
114+ const auto max_block_rows = sanisizer ::min (length, options. primary_block_size );
112115 tmp_output.emplace (sanisizer::product<I<decltype (tmp_output->size ())> >(max_block_cols, max_block_rows));
113116 optr = tmp_output->data ();
114117 } else {
115118 optr = output; // no need to add 'start' as this is zero in the single-threaded case.
116119 }
117120
118- LeftIndex_ r = 0 ;
119- while (r < length) {
120- const LeftIndex_ rnum = std ::min<LeftIndex_>( primary_block_size, length - r );
121- for (LeftIndex_ rcounter = 0 ; rcounter < rnum ; ++rcounter ) {
122- lptrs[rcounter ] = ext->fetch (lbuffers[rcounter ].data ());
121+ LeftIndex_ lr = 0 ;
122+ while (lr < length) {
123+ const LeftIndex_ lr_num = sanisizer ::min(options. primary_block_size , length - lr );
124+ for (LeftIndex_ lr_counter = 0 ; lr_counter < lr_num ; ++lr_counter ) {
125+ lptrs[lr_counter ] = ext->fetch (lbuffers[lr_counter ].data ());
123126 }
124127
125- RightIndex_ h = 0 ;
126- while (h < num_rhs ) {
127- const RightIndex_ hnum = sanisizer::min (primary_block_size, num_rhs - h );
128+ RightIndex_ rc = 0 ;
129+ while (rc < right_NC ) {
130+ const RightIndex_ rc_num = sanisizer::min (options. primary_block_size , right_NC - rc );
128131
129- RightIndex_ row_offset, col_offset, stride;
132+ LeftIndex_ out_row_offset;
133+ RightIndex_ out_col_offset, out_stride;
130134 if (do_parallel) {
131- std::fill_n (optr, sanisizer::product_unsafe<std::size_t >(hnum, rnum ), 0 );
132- row_offset = 0 ;
133- col_offset = 0 ;
134- stride = hnum ;
135+ std::fill_n (optr, sanisizer::product_unsafe<std::size_t >(rc_num, lr_num ), 0 );
136+ out_row_offset = 0 ;
137+ out_col_offset = 0 ;
138+ out_stride = rc_num ;
135139 } else {
136- row_offset = r ; // start == 0 in serial code, so no need to add start here.
137- col_offset = h ;
138- stride = num_rhs ;
140+ out_row_offset = lr ; // start == 0 in serial code, so no need to add start here.
141+ out_col_offset = rc ;
142+ out_stride = right_NC ;
139143 }
140144
141- LeftIndex_ c = 0 ;
142- while (c < NC ) {
143- const LeftIndex_ cnum = std ::min<LeftIndex_>( secondary_block_size, NC - c );
144- for (LeftIndex_ rcounter = 0 ; rcounter < rnum ; ++rcounter ) {
145- const auto leftrow = lptrs[rcounter ];
146- for (RightIndex_ hcounter = 0 ; hcounter < hnum ; ++hcounter ) {
147- auto & dest = optr[sanisizer::nd_offset<std::size_t >(col_offset + hcounter, stride, row_offset + rcounter )];
145+ LeftIndex_ cd = 0 ;
146+ while (cd < common_dim ) {
147+ const LeftIndex_ cd_num = sanisizer ::min(options. secondary_block_size , common_dim - cd );
148+ for (LeftIndex_ lr_counter = 0 ; lr_counter < lr_num ; ++lr_counter ) {
149+ const auto leftrow = lptrs[lr_counter ];
150+ for (RightIndex_ rc_counter = 0 ; rc_counter < rc_num ; ++rc_counter ) {
151+ auto & dest = optr[sanisizer::nd_offset<std::size_t >(out_col_offset + rc_counter, out_stride, out_row_offset + lr_counter )];
148152 dest = dense_dot_product<accumulators_>(
149- cnum,
150- right[h + hcounter ] + c ,
151- leftrow + c ,
153+ cd_num, // cast to size_t is safe due to tatami's contract.
154+ right_ptrs[rc + rc_counter ] + cd ,
155+ leftrow + cd ,
152156 dest
153157 );
154158 }
155159 }
156- c += cnum ;
160+ cd += cd_num ;
157161 }
158162
159163 if (do_parallel) {
160- for (LeftIndex_ rcounter = 0 ; rcounter < rnum ; ++rcounter ) {
164+ for (LeftIndex_ lr_counter = 0 ; lr_counter < lr_num ; ++lr_counter ) {
161165 std::copy_n (
162- optr + sanisizer::product_unsafe<std::size_t >(hnum, rcounter ),
163- hnum ,
164- output + sanisizer::nd_offset<std::size_t >(h, num_rhs , start + r + rcounter )
166+ optr + sanisizer::product_unsafe<std::size_t >(rc_num, lr_counter ),
167+ rc_num ,
168+ output + sanisizer::nd_offset<std::size_t >(rc, right_NC , start + lr + lr_counter )
165169 );
166170 }
167171 }
168172
169- h += hnum ;
173+ rc += rc_num ;
170174 }
171- r += rnum ;
175+ lr += lr_num ;
172176 }
173- }, NR , options.num_threads );
177+ }, left_NR , options.num_threads );
174178}
175179
176180}
0 commit comments