Одна из возможностей - просто разделить ваш подход einsum
.Нарезка rows
и cols
на биты размером 20 решает большую (10 ^ 7) проблему за ~ 2 минуты на моем ноутбуке.Возможно, это можно улучшить, изменив размер фрагмента.
Но мы можем добиться большего: мы можем сгруппировать либо по строкам, либо по столбцам (я выбрал столбцы), а затем умножить отдельные столбцы на все парные строки.Мы можем использовать разреженные матрицы csc / csr, чтобы выполнить всю сортировку / перемешивание / переиндексацию за нас.Этот метод на тех же данных заканчивается через ~ 30 сек.
import numpy as np
from scipy import sparse
def f_sparse_helper(a, b, rows, cols):
h = sparse.csr_matrix((np.empty(L), cols, np.arange(L+1)), (L, M)) \
.tocsc()
for i in range(M):
l, r = h.indptr[i:i+2]
h.data[l:r] = a[rows[h.indices[l:r]]] @ b[:, i]
return h.tocsr().data
def f_chunk(a, b, rows, cols, chunk=20):
out = np.empty(L)
for j in range(0, rows.size, chunk):
l = j+chunk
out[j:l] = np.einsum("ij,ji->i", a[rows[j:l]], b[:,cols[j:l]])
return out
def prep_data(K, M, N, L):
a = np.random.uniform(0, 10, (N, K))
b = np.random.uniform(0, 10, (K, M))
rows = np.random.randint(0, N, (L,))
cols = np.random.randint(0, M, (L,))
return a, b, rows, cols
# use small exmpl to check correct
K, M, N, L = 10, 100, 100, 1000
a, b, rows, cols = prep_data(K, M, N, L)
res = f_sparse_helper(a, b, rows, cols)
assert np.allclose(res, np.einsum("ij,ji->i", a[rows], b[:,cols]))
assert np.allclose(res, f_chunk(a, b, rows, cols))
# timeit on big one
from time import perf_counter as pc
K, M, N, L = 1_000, 10_000, 10_000, 10_000_000
a, b, rows, cols = prep_data(K, M, N, L)
t = pc()
res_ch = f_chunk(a, b, rows, cols)
s = pc()
print('chunked ', s-t, 'seconds')
t = pc()
res_sh = f_sparse_helper(a, b, rows, cols)
s = pc()
print('sparse helper', s-t, 'seconds')
assert np.allclose(res_sh, res_ch)
Пример выполнения:
chunked 121.16188396583311 seconds
sparse helper 31.172512074932456 seconds