如何高效并行实现稀疏二维矩阵argpartition以获取每行top-k元素
前置优化:先消除原生循环的冗余开销
你当前的for循环中每行重复调用3次getrow(),每次都会生成新的稀疏行对象,开销极高。先做这步优化,性能就能提升5~10倍,比直接上并行收益更高:
from scipy.sparse import csr_matrix import numpy as np def sparse_row_topk(sparse_matrix, n_best=1): res = np.zeros((sparse_matrix.shape[0], n_best), dtype=np.int64) # 直接获取CSR矩阵三个底层数组,避免重复创建行对象 data = sparse_matrix.data indices = sparse_matrix.indices indptr = sparse_matrix.indptr for i in range(sparse_matrix.shape[0]): start, end = indptr[i], indptr[i+1] if end - start == 0: continue k = min(n_best, end - start) topk_pos = data[start:end].argpartition(-k)[-k:] topk_indices = indices[start:end][topk_pos] res[i, :k] = topk_indices return res
并行优化方案
方案1:Joblib多进程并行
适合矩阵行数超过1000的场景,直接利用多核CPU资源,代码修改量极小:
from joblib import Parallel, delayed import numpy as np from scipy.sparse import csr_matrix def process_single_row(data_row, indices_row, n_best): nnz = len(data_row) if nnz == 0: return np.zeros(n_best, dtype=np.int64) k = min(n_best, nnz) topk_pos = data_row.argpartition(-k)[-k:] topk_indices = indices_row[topk_pos] res = np.zeros(n_best, dtype=np.int64) res[:k] = topk_indices return res def sparse_topk_parallel(sparse_matrix, n_best=1, n_jobs=-1): data = sparse_matrix.data indices = sparse_matrix.indices indptr = sparse_matrix.indptr rows = Parallel(n_jobs=n_jobs)( delayed(process_single_row)( data[indptr[i]:indptr[i+1]], indices[indptr[i]:indptr[i+1]], n_best ) for i in range(sparse_matrix.shape[0]) ) return np.vstack(rows)
如果行数少于1000,进程通信开销会超过并行收益,不建议使用该方案。
方案2:Numba JIT无开销并行
这是目前CPU端性能最高的实现,Numba会直接把Python循环编译成优化的机器码,支持自动并行,没有进程通信的额外开销,性能比多进程版本高2~5倍:
首先安装依赖:pip install numba
import numpy as np from numba import njit, prange from scipy.sparse import csr_matrix @njit(parallel=True) def _sparse_topk_numba(data, indices, indptr, n_best, n_rows): res = np.zeros((n_rows, n_best), dtype=np.int64) # prange会自动把循环分配到多个CPU核心并行执行 for i in prange(n_rows): start = indptr[i] end = indptr[i+1] nnz = end - start if nnz == 0: continue k = min(n_best, nnz) row_data = data[start:end] row_indices = indices[start:end] topk_pos = row_data.argpartition(-k)[-k:] res[i, :k] = row_indices[topk_pos] return res def sparse_topk_numba(sparse_matrix, n_best=1): return _sparse_topk_numba( sparse_matrix.data, sparse_matrix.indices, sparse_matrix.indptr, n_best, sparse_matrix.shape[0] )
方案3:GPU加速(可选,有NVIDIA显卡时可用)
如果显存足够放下稠密矩阵,用CuPy实现性能比CPU并行高10倍以上:
首先安装依赖:pip install cupy-cuda11x(对应你的CUDA版本)
import cupy as cp from cupyx.scipy.sparse import csr_matrix as cp_csr_matrix # 将scipy稀疏矩阵转到GPU显存 gpu_sparse = cp_csr_matrix(sparse_matrix) n_best = 1 # 直接逐行做argpartition res = cp.argpartition(gpu_sparse.todense(), -n_best)[:, -n_best:] # 结果转回到CPU numpy数组 res_cpu = cp.asnumpy(res)
内容的提问来源于stack exchange,提问作者MarinPitavy
相关产品推荐
相关产品推荐

