2013-02-21 13 views
40

Spesso nei miei loop interni ho bisogno di indicizzare un array in un modo "wrap-around", in modo che se la dimensione dell'array è 100 e il mio codice richiede l'elemento -2 , dovrebbe essere fornito l'elemento 98. In molti linguaggi di alto livello come Python, si può fare semplicemente con my_array[index % array_size], ma per qualche motivo l'intero aritmetico di C (di solito) arrotonda verso zero invece di arrotondare costantemente verso il basso, e di conseguenza il suo operatore modulo ritorna un risultato negativo quando viene dato un primo argomento negativo.Il modo più veloce per ottenere un modulo positivo in C/C++

Spesso so che lo index non sarà inferiore a -array_size e in questi casi faccio solo my_array[(index + array_size) % array_size]. Tuttavia, a volte questo non può essere garantito, e per quei casi mi piacerebbe sapere il modo più veloce per implementare una funzione modulo sempre positiva. Ci sono diversi modi "intelligenti" per farlo senza ramificazione, come

inline int positive_modulo(int i, int n) { 
    return (n + (i % n)) % n 
} 

o

inline int positive_modulo(int i, int n) { 
    return (i % n) + (n * (i < 0)) 
} 

Certo che posso profilo questi per scoprire quale è il più veloce sul mio sistema, ma posso Aiutatemi a preoccuparmi che possa essermi perso uno migliore, o che quello che è veloce sulla mia macchina potrebbe essere lento su uno diverso.

Quindi c'è un modo standard per fare questo, o qualche trucco intelligente che ho perso che è probabile che sia il modo più veloce possibile?

Inoltre, so che probabilmente è un pio desiderio, ma se c'è un modo per farlo che può essere auto-vettorializzato, sarebbe fantastico.

+0

Stai modificando costantemente lo stesso numero? – Mysticial

+0

@Mysticial in genere, sì. – Nathaniel

+0

@Mysticial anche se la soluzione vincola il numero su cui sto modificando per essere una potenza di 2, va bene. – Nathaniel

risposta

20

Modulo una potenza di due, le seguenti opere (due a due assumendo completano la rappresentazione):

return i & (n-1); 
+0

Mille grazie! Lascerò la domanda aperta nel caso qualcuno abbia una buona risposta per il caso generale, ma probabilmente finirò per usarlo. – Nathaniel

+0

Non capisco questa soluzione, potresti spiegare per favore? Ad esempio, se abbiamo 7 mod 2 -> 0111 mod 0010 -> 0110 & 0010 = 2 e dovrebbe essere 1. Cosa mi manca? – ixSci

+0

Si utilizza 'n-1', non' n'. Quindi in questo caso, sarebbe '0111 & 1 = 1'. Si noti che quando 'n' è un potere di due,' n-1' è costituito da tutti. – nneonneo

49

Il metodo standard che ho imparato è

inline int positive_modulo(int i, int n) { 
    return (i % n + n) % n; 
} 

Questa funzione è essenzialmente la vostra prima variante senza la abs (che, di fatto, fa ritornare il risultato sbagliato). Non sarei sorpreso se un compilatore ottimizzante potesse riconoscere questo modello e compilarlo al codice macchina che calcola un "modulo senza segno".

Edit:

Passando alla seconda variante: Prima di tutto, contiene un bug, anche - il n < 0 dovrebbe essere i < 0.

Questa variante potrebbe non sembrare una diramazione, ma su molte architetture, lo i < 0 verrà compilato in un salto condizionato. In ogni caso, sarà altrettanto rapido sostituire (n * (i < 0)) con i < 0? n: 0, che evita la moltiplicazione; inoltre, è "più pulito" perché evita di reinterpretare il bool come int.

Per quanto riguarda quale di queste due varianti è più veloce, ciò dipende probabilmente dal compilatore e dall'architettura del processore: tempo le due varianti e vedere. Non penso che ci sia un modo più veloce di una di queste due varianti, però.

+0

Nitpick: In realtà non si vectorize perché in genere non esiste il supporto SIMD per il modulo. – Mysticial

+0

@Mysticial: buon punto - rimuoverò quella nota. –

+1

Sarebbe più efficiente inserire il 'n' in un modello? Nel caso in cui la funzione non possa essere sottolineata, il compilatore potrebbe essere in grado di giocare alcuni trucchi per migliorare le prestazioni. –

1

Si può anche fare array[(i+array_size*N) % array_size], dove N è un numero intero abbastanza grande da garantire argomenti positivi, ma abbastanza piccolo da non eccedere.

Quando array_size è costante, esistono tecniche per calcolare il modulo senza divisione.Oltre alla potenza di due approcci, è possibile calcolare una somma ponderata di gruppi di bit moltiplicata per 2^i% n, dove i è il bit meno significativo in ciascun gruppo:

, ad es. Intero a 32 bit 0xaabbccdd% 100 = dd + cc * [2] 56 + bb * [655] 36 + aa * [167772] 16, con l'intervallo massimo di (1 + 56 + 36 + 16) * 255 = 27795. Con applicazioni ripetute e suddivisioni diverse si può ridurre l'operazione a poche sottrazioni condizionali.

Le pratiche comuni includono anche l'approssimazione della divisione con reciproco di 2^32/n, che di solito può gestire un intervallo di argomenti ragionevolmente ampio.

i - ((i * 655)>>16)*100; // (gives 100*n % 100 == 100 requiring adjusting...) 
5

Un modo vecchia scuola per ottenere l'addendo opzionale utilizzando complemento a due sign-bit propagazione:

int positive_mod(int i, int n) 
{ 
    /* constexpr */ int shift = CHAR_BIT*sizeof i - 1; 
    int m = i%n; 
    return m+ (m>>shift & n); 
} 
+0

vecchia scuola difficile da leggere mod . Mi piace. Anche se mi chiedo se '(i >> shift & n)' potrebbe essere più veloce, altrimenti l'operazione bitshift dovrà aspettare che l'operazione modulo termini. – aaaaaaaaaaaa

+0

Sarebbe più veloce ma darebbe risultati errati per es. -2 mod 2. – jthill

+0

Spara, hai ragione. E ora che ne parli, vale anche per '(i% n) + (n * (i <0))'. – aaaaaaaaaaaa

0

Il secondo esempio è migliore del primo. Una moltiplicazione è un'operazione più complessa di un'operazione if/else, quindi usa:

inline int positive_modulo(int i, int n) { 
    int tmp = i % n; 
    return tmp ? i >= 0 ? tmp : tmp + n : 0; 
} 
+0

1) hai ragione, ho modificato il codice. 2) se i è negativo il ritorno è un negativo, i% n restituisce un numero negativo, ad esempio -102% a 100 ritorna -2 così u basta aggiungere n al risultato – SkYWAGz

+0

cura il codice, che è il meglio che posso fare. – SkYWAGz

+0

1) Forse semplicemente 'return tmp <0? tmp + n: tmp; '. 2) Questa risposta ha un vantaggio su [molto quotata] (http://stackoverflow.com/a/14997413/2410359) in quanto non trabocca mai. – chux

Problemi correlati