Potrebbe essere una risposta troppo specifica, ma su una piattaforma ARM che supporta la NEON, la vettorizzazione NEON può essere utilizzata per eseguire una copia stridente ancora più veloce. Ciò potrebbe salvare la vita in un ambiente in cui le risorse sono relativamente più limitate, il che probabilmente è il motivo per cui ARM viene utilizzato in quell'impostazione in primo luogo. Un esempio di spicco è Android, dove la maggior parte dei dispositivi utilizza ancora l'architettura ARM v7a che supporta NEON.
I seguenti esempi lo dimostrano, è un loop per copiare il piano UV semipiano di un'immagine YUV420sp nel piano UV planare di un'immagine YUV420p. Le dimensioni dei buffer di origine e di destinazione sono entrambi di 640*480/2
byte. Tutti gli esempi sono compilati con g ++ 4.8 all'interno di Android NDK r9d. Vengono eseguiti su un processore Samsung Exynos 5420 Octa:
Livello 1: regolare
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
Compilato con solo -O3
, dura circa 1,5 ms in media.
Livello 2: Unrolled e strinse un po 'più con i puntatori in movimento
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
unsigned char* endptr = dstptr + stride;
while(dstptr<endptr){
*(dstptr + 0) = *(srcptr + 0);
*(dstptr + stride + 0) = *(srcptr + 1);
*(dstptr + 1) = *(srcptr + 2);
*(dstptr + stride + 1) = *(srcptr + 3);
*(dstptr + 2) = *(srcptr + 4);
*(dstptr + stride + 2) = *(srcptr + 5);
*(dstptr + 3) = *(srcptr + 6);
*(dstptr + stride + 3) = *(srcptr + 7);
*(dstptr + 4) = *(srcptr + 8);
*(dstptr + stride + 4) = *(srcptr + 9);
*(dstptr + 5) = *(srcptr + 10);
*(dstptr + stride + 5) = *(srcptr + 11);
*(dstptr + 6) = *(srcptr + 12);
*(dstptr + stride + 6) = *(srcptr + 13);
*(dstptr + 7) = *(srcptr + 14);
*(dstptr + stride + 7) = *(srcptr + 15);
srcptr+=16;
dstptr+=8;
}
}
compilati con solo -O3
, dura circa 1,15 ms in media. Questo è probabilmente il più veloce che si ottiene su un'architettura normale, come per l'altra risposta.
Livello 3: regolare + GCC NEON automatica vettorializzazione
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
Compilato con -O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfp
, dura circa 0,6 ms in media. Per riferimento, un memcpy
di 640*480
byte o il doppio della quantità di ciò che viene testato qui, richiede in media circa 0,6 ms.
Come nota a margine, il secondo codice (srotolato e puntato) compilato con i parametri NEON sopra richiede circa lo stesso tempo, 0.6 ms.
Uno standard per loop esegue almeno la velocità di uno standard per loop ... Sarcasm a parte, dipende dalla struttura di archiviazione dei dati che stai utilizzando. Per gli array, non penso che tu possa fare meglio di un ciclo for, incrementato dal tuo modulo. – ChrisCM
'memcpy' è talvolta più veloce di un ciclo' for' grazie all'ottimizzazione che può eseguire perché la memoria su cui sta operando è contigua. Quelle ottimizzazioni non possono essere fatte qui. –
@dauphic Ma allora perché CUDA ha 'cudaMemcpy2D' che copia con intonazione? – JackOLantern