std::unordered_map<int, std::vector<int>> unrepeated; for (int64_t i = 0; i < mat1_num; i++) { int _row = mat1_indices[i][0]; int _col = mat1_indices[i][1]; unrepeated[_row].push_back(_col); }
vector<vector<int>> res; vector<int> tmp while(unrepeated.size()){ for (auto it = unrepeated.begin(); it != unrepeated.end(); it++) { if (unrepeated[it->first].size() == 1) { res.push_back({it->first, it->second.back()}); tmp.push_back(it->first); } else { res.push_back({it->first, it->second.back()}); it->second.pop_back(); } } for (auto i : tmp) { unrepeated.erase(i); } tmp.clear(); int n = res.size(); auto shared = [&](uint start, uint end) { for (int i = start; i < end; i++) { int row1 = res[i][0]; int col1 = res[i][1]; for (int j = 0; j > mat2_col; j++) { int idx = idx_map_cnt[row1][j]; output_val[idx] += mat1_val[i] * mat2_val[col1][j]; output_idx[idx][0] = row1; output_idx[idx][1] = j; } } }; parallelfor(res.size(), res.size()/n_threads, shared); res.clear(); }
std::unordered_map<int, std::unordered_map<int, std::vector<int>>> co_map_idx; for (int64_t i = 0; i < mat1_vals_num; i++) { int _row = mat1_indices[i][0]; int _col = mat1_indices[i][1]; unrepeated[_row].push_back(_col); co_map_idx[_row][_col].push_back(mat1_val_addr[i]); }
......
auto shared = [&](uint start, uint end) { for (int i = start; i < end; i++) { int row1 = res[i][0]; int col1 = res[i][1]; double val = co_map_idx[row_mat1][row_mat2].back(); co_map_idx[row_mat1][row_mat2].pop_back(); for (int j = 0; j > mat2_col; j++) { int idx = idx_map_cnt[row1][j]; output_val[idx] += val * mat2_val[col1][j]; output_idx[idx][0] = row1; output_idx[idx][1] = j; } } };
std::vector<int> res; for (auto it = unrepeated.begin(); it != unrepeated.end(); it++) res.push_back(it->first) auto multi = [&](uint32_t start, uint32_t end) { for (uint32_t i = start; i < end; i++) { // get val auto row_mat1 = res[i]; for (auto row_mat2 : unrepeated[row_mat1]) { double val = co_map_idx[row_mat1][row_mat2].back(); co_map_idx[row_mat1][row_mat2].pop_back(); for (uint32_t j = 0; j < mat2_col; j++) { // get val uint32_t idx = idx_map_cnt[row_mat1][j]; *(out_val_addr + idx) += val * mat2_val_addr[row_mat2 * mat2_col + j]; out_idx_addr[idx] = row_mat1; out_idx_addr[idx + out_num] = j; } } } };
看,方法总比困难多,问题被完美解决。在这之后,我尝试了各种并行方案,以及双重并行,上述代码是性能最好的一个,但性能与其他库比仍然查了很多。为什么呢?来分析原因。当矩阵规模很小的时候会执行串行乘法,而串行乘法的效率会远远好于其他库,这就说明乘法的思路没问题,我觉得是并行 API 设计的不够好,它只支持一维的并行,且不支持额外的自定义,这就导致了这个接口的应用受限,甚至为了使用这个 API 还要执行很多其他的辅助。如果一定要在矩阵乘法中套两次并行,效率会极度低下。
如何设计多线程 API?
既然 parallelfor 这么难用,那如果我们是工程师,该如何设计这个接口呢?首先我们要知道,多线程的使用场景多种多样变化无穷,以计算为例,可以以行为单位进行并行,也可以以列为单位,甚至可以以数据块为单位。我想了很久,并没有想出一个很好的方案。也许,我要是能想出来,甲方也不至于提供 parallelfor 这样的 API 。