È difficile credere che il costrutto p[u+1]
si verifichi in più punti negli anelli di codice più interni, in modo tale che ottenere la micro ottimizzazione di esso faccia sì ore di differenza in un'operazione che viene eseguita per giorni.Puntatore di ottimizzazione micro + senza segno + 1
In genere *((p+u)+1)
è più efficiente. A volte *(p+(u+1))
è più efficiente. Raramente *((p+1)+u)
è la cosa migliore. (Ma solitamente un ottimizzatore può convertire *((p+1)+u)
in *((p+u)+1)
quando quest'ultimo è migliore, e non può convertire *(p+(u+1))
con nessuno degli altri).
p
è un puntatore e u
è un non firmato. Nel codice attuale almeno uno di essi (più probabilmente entrambi) sarà già nei registri nel punto in cui viene valutata l'espressione. Questi fatti sono fondamentali al punto della mia domanda.
In 32-bit (prima che il mio progetto lasciasse il supporto per questo) tutti e tre hanno esattamente la stessa semantica e qualsiasi compilatore mezzo decente sceglie semplicemente il meglio dei tre e il programmatore non ha mai bisogno di preoccuparsi.
In questi usi a 64 bit, il programmatore sa che tutti e tre hanno la stessa semantica, ma il compilatore non lo sa. Per quanto il compilatore sa, la decisione su quando estendere u
da 32-bit a 64-bit può influire sul risultato.
Qual è il modo più pulito per dire al compilatore che la semantica di tutti e tre è la stessa e il compilatore dovrebbe selezionare il più veloce di essi?
In un Linux a 64-bit compilatore, ho avuto quasi arrivati con p[u+1L]
che fa sì che il compilatore per selezionare in modo intelligente tra il genere migliore *((p+u)+1)
e talvolta meglio *(p+((long)(u) + 1))
. Nel raro caso *(p+(u+1))
era ancora meglio del secondo di quelli, un po 'è perso.
Ovviamente, ciò non va bene in Windows a 64 bit. Ora che abbiamo abbandonato il supporto a 32 bit, forse p[u+1LL]
è abbastanza portatile e abbastanza buono. Ma posso fare di meglio?
Si noti che l'utilizzo di std::size_t
anziché unsigned
per u
eliminerebbe l'intero problema, ma crea un problema di prestazioni più ampio nelle vicinanze. Casting u
a std::size_t
giusto c'è abbastanza buono, e forse il meglio che posso fare. Ma questo è piuttosto prolisso per una soluzione imperfetta.
La semplice codifica (p+1)[u]
rende una selezione più probabile che sia ottimale rispetto a p[u+1]
. Se il codice fosse meno basato sui modelli e più stabile, potrei impostarli tutti su (p+1)[u]
quindi profilo, quindi passare di nuovo a p[u+1]
. Ma il modello tende a distruggere quell'approccio (Una singola riga di origine appare in molti posti nel profilo che aggiungono fino a tempi serie, ma non individualmente tempo serio).
I compilatori che dovrebbero essere efficienti per questo sono GCC, ICC e MSVC.
'Tipicamente * ((p + u) +1) è più efficiente. L'hai misurato? sembra un cattivo profilo. Non mi aspetto alcuna differenza di prestazioni. –
Ho appena acceso la mia macchina Linux, sto controllando lo stesso @JSF. Vedremo come andrà . –
@DavidHaim, sì ho misurato attraverso la modifica originale. Se conosci l'assemblatore x86-64, dovresti sapere perché '* ((p + u) +1)' è in genere più efficiente. – JSF