Sto scrivendo una libreria di algebra lineare in Rust.Perché il mio codice viene eseguito più lentamente quando rimuovo i controlli sui limiti?
Ho una funzione per ottenere un riferimento ad una cella di matrice a una data riga e colonna. Questa funzione inizia con un paio di affermazioni che la riga e la colonna sono entro limiti:
#[inline(always)]
pub fn get(&self, row: usize, col: usize) -> &T {
assert!(col < self.num_cols.as_nat());
assert!(row < self.num_rows.as_nat());
unsafe {
self.get_unchecked(row, col)
}
}
In cicli stretti, ho pensato che potrebbe essere più veloce di saltare controllare i limiti, quindi fornire un metodo get_unchecked
:
#[inline(always)]
pub unsafe fn get_unchecked(&self, row: usize, col: usize) -> &T {
self.data.get_unchecked(self.row_col_index(row, col))
}
La cosa strana è, quando uso questi metodi per implementare moltiplicazione matriciale (tramite iteratori riga e colonna), miei benchmark mostrano che in realtà va circa 33% più velocemente quando controllare i limiti. Perché sta succedendo?
Ho provato questo su due computer diversi, uno con Linux e l'altro OSX, ed entrambi mostrano l'effetto.
Il codice completo è on github. Il file pertinente è lib.rs. Funzioni di interesse sono:
get
alla riga 68get_unchecked
alla riga 81next
alla riga 551mul
a alla riga 1038
matrix_mul
(benchmark) Si noti che sto usando i numeri di tipo a livello di parametrizzare il mio matrici (con l'opzione per le dimensioni dinamiche anche tramite tipi con tag dummy), quindi il benchmark sta moltiplicando due matrici 100x100.
UPDATE:
ho significativamente semplificato il codice, eliminando roba non utilizzati direttamente nel benchmark e la rimozione di parametri generici. Ho anche scritto un'implementazione della moltiplicazione senza l'utilizzo di iteratori, e che la versione non provoca lo stesso effetto. Vedi here per questa versione del codice. La clonazione del ramo minimal-performance
e l'esecuzione di cargo bench
eseguiranno il benchmark delle due diverse implementazioni di moltiplicazione (si noti che le affermazioni sono commentate per iniziare in quel ramo).
Degna di nota è che se cambio le get*
funzioni per restituire copie dei dati invece di riferimenti (f64
invece di &f64
), l'effetto scompare (ma il codice è leggermente più lento tutto).
La vecchia domanda di nuovo: hai compilato le ottimizzazioni del compilatore (con il flag '--release')? ;) –
Senza avere alcuna idea in merito alla ruggine: il tuo benchmarking è sano di mente? Effetti di cache, varianza, sincronizzazione dei dati di test ... – sascha
@LukasKalbertodt Sì, eseguo i miei benchmark con 'cargo bench', che viene compilato automaticamente con il rilascio. –