Ho fatto domande correlate su questo stack per la settimana passata per cercare di isolare le cose che non ho capito sull'uso del decoratore @jit con Numba in Python. Comunque, sto sbattendo contro il muro, quindi scriverò solo l'intero problema.Distanza segmento inter utilizzando numba jit, Python
Il problema è quello di calcolare la distanza minima tra le coppie di un numero di segmenti grande. I segmenti sono rappresentati dal loro inizio e dai loro endpoint in 3D. Matematicamente, ogni segmento è parametrizzato come [AB] = A + (B-A) * s, dove s in [0,1], e A e B sono l'inizio e il punto finale del segmento. Per due di questi segmenti, la distanza minima può essere calcolata e la formula è data here.
Ho già esposto questo problema su un altro thread e la risposta fornita riguardava la sostituzione di doppi loop del mio codice mediante la vettorizzazione del problema, che tuttavia avrebbe colpito problemi di memoria per insiemi di segmenti di grandi dimensioni. Pertanto, ho deciso di restare con i loop e utilizzare invece lo jolly di numba.
Poiché la soluzione del problema richiede molti prodotti dot e il prodotto con punti di Numpy è not supported by numba, ho iniziato implementando il mio prodotto 3D a punti.
import numpy as np
from numba import jit, autojit, double, float64, float32, void, int32
def my_dot(a,b):
res = a[0]*b[0]+a[1]*b[1]+a[2]*b[2]
return res
dot_jit = jit(double(double[:], double[:]))(my_dot) #I know, it's not of much use here.
La funzione di calcolo della distanza minima di tutte le coppie di segmenti N prende come input un array NX6 (6) coordinate
def compute_stuff(array_to_compute):
N = len(array_to_compute)
con_mat = np.zeros((N,N))
for i in range(N):
for j in range(i+1,N):
p0 = array_to_compute[i,0:3]
p1 = array_to_compute[i,3:6]
q0 = array_to_compute[j,0:3]
q1 = array_to_compute[j,3:6]
s = (dot_jit((p1-p0),(q1-q0))*dot_jit((q1-q0),(p0-q0)) - dot_jit((q1-q0),(q1-q0))*dot_jit((p1-p0),(p0-q0)))/(dot_jit((p1-p0),(p1-p0))*dot_jit((q1-q0),(q1-q0)) - dot_jit((p1-p0),(q1-q0))**2)
t = (dot_jit((p1-p0),(p1-p0))*dot_jit((q1-q0),(p0-q0)) -dot_jit((p1-p0),(q1-q0))*dot_jit((p1-p0),(p0-q0)))/(dot_jit((p1-p0),(p1-p0))*dot_jit((q1-q0),(q1-q0)) - dot_jit((p1-p0),(q1-q0))**2)
con_mat[i,j] = np.sum((p0+(p1-p0)*s-(q0+(q1-q0)*t))**2)
return con_mat
fast_compute_stuff = jit(double[:,:](double[:,:]))(compute_stuff)
Quindi, compute_stuff (arg) prende come argomento un np.array 2D (double [:,:]), esegue una serie di operazioni supportate da numba (?) e restituisce un altro np.array 2D (double [:,:]). Tuttavia,
v = np.random.random((100,6))
%timeit compute_stuff(v)
%timeit fast_compute_stuff(v)
Ottengo 134 e 123 ms per ciclo. Puoi far luce sul motivo per cui non riesco ad accelerare la mia funzione? Qualsiasi feedback sarebbe molto apprezzato.
È * molto * improbabile che sia possibile battere 'np.dot' utilizzando il compilatore JIT di numba. 'np.dot' è solo un involucro sottile che chiama le funzioni BLAS' * gemm/* gemv', che sono fortemente ottimizzate e spesso multithread. La tua scommessa migliore è probabilmente quella di assicurarsi che Numpy sia collegato alla libreria BLAS con multithreading più veloce che puoi mettere sulle tue mani (probabilmente sia la MKL di Intel che OpenBLAS). –
il problema non sta battendo np.dot, il problema è che se il compilatore jit viene eseguito in una chiamata np.dot, non può inferire il suo tipo restituito e quindi non velocizzerà tutta la mia funzione (e btw, dot_jit che ho codificato è più veloce di np.dot per i prodotti scalari del vettore 3d) – Mathusalem
Hai profilato il tuo codice originale? Sospetto che tu stia spendendo la maggior parte del tuo tempo all'interno di 'np.dot' comunque, quindi non dovresti aspettarti troppi benefici dalle prestazioni eliminando il sovraccarico dai cicli' for' nidificati. –