Как эффективно работать с комбинациями по одному измерению в массиве np
Дано S
быть n x m
матрица, как массив NumPy, я хочу, чтобы вызвать функцию f
на пары (S[i], S[j])
рассчитать конкретное значение интереса, хранящееся в матрице с размерами n x n
, В моем конкретном случае функция f
коммутативно так f(x,y) = f(y,x)
,
Имея все это в виду, мне интересно, могу ли я сделать какие-то трюки, чтобы максимально ускорить это, n
может быть довольно большим.
Когда я измеряю время функции f, она составляет пару микросекунд, как и ожидалось. Это довольно простой расчет. Ниже я покажу вам время, которое я получил, по сравнению с max()
а также sum()
для справки.
In [19]: %timeit sum(s[11])
4.68 µs ± 56.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [20]: %timeit max(s[11])
3.61 µs ± 64.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [21]: %timeit f(s[11], s[21], 50, 10, 1e-5)
1.23 µs ± 7.25 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [22]: %timeit f(s[121], s[321], 50, 10, 1e-5)
1.26 µs ± 31.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Однако, когда я рассчитываю общее время обработки для данных выборки 500x50 (в результате получается сравнение 500 x 500 /2 = 125K), общее время значительно увеличивается (в минутах). Я бы ожидал что-то вроде 0,2-0,3 секунды (1,25 * 2E-6 с / расч.).
In [12]: @jit
...: def testf(s, n, m, p):
...: tol = 1e-5
...: sim = np.zeros((n,n))
...: for i in range(n):
...: for j in range(n):
...: if i > j:
...: delta = p[i] - p[j]
...: if delta < 0:
...: res = f(s[i], s[j], m, abs(delta), tol) # <-- max(s[i])
...: else:
...: res = f(s[j], s[i], m, delta, tol) # <-- sum(s[j])
...: sim[i][j] = res
...: sim[j][i] = res
...: return sim
В коде выше я изменил строки, где res
назначен на max()
а также sum()
(закомментированные части) для тестирования, и код выполняется примерно в 100 раз быстрее, хотя сами функции медленнее по сравнению с моей функцией f()
Что подводит меня к моим вопросам:
Можно ли избежать двойной петли, чтобы ускорить это? В идеале я хочу иметь возможность запустить это для матриц, где размер n = 1E5. (Комментарий: поскольку функции max и sum работают значительно быстрее, я предполагаю, что циклы for здесь не являются узким местом, но все же полезно знать, есть ли лучший способ)
Что может вызвать серьезное замедление с моей функцией, если это не двойной цикл for?
РЕДАКТИРОВАТЬ
Специфика функции f была задана некоторыми комментариями. Он перебирает два массива и проверяет количество значений в двух массивах, которые "достаточно близки". Я удалил комментарии и изменил некоторые имена переменных, но логика такая, как показано ниже. Было интересно отметить, что math.isclose(x,y,rel_tol)
что эквивалентно операторам if, которые у меня есть ниже, делает код значительно медленнее, возможно, из-за библиотечного вызова?
from numba import jit
@jit
def f(arr1, arr2, n, d, rel_tol):
counter = 0
i,j,k = 0,0,0
while (i < n and j < n and k < n):
val = arr1[j] + d
if abs(arr1[i] - arr2[k]) < rel_tol * max(arr1[i], arr2[k]):
counter += 1
i += 1
k += 1
elif abs(val - arr2[k]) < rel_tol * max(val, arr2[k]):
counter += 1
j += 1
k += 1
else:
# incremenet the index corresponding to the lightest
if arr1[i] <= arr2[k] and arr1[i] <= val:
if i < n:
i += 1
elif val <= arr1[i] and val <= arr2[k]:
if j < n:
j += 1
else:
k += 1
return counter