2013-06-13 10 views
8

Ho due array e voglio copiare un array nell'altro con qualche passo. Ad esempio, hoCopia di dati a passi in C++

A A A A A A A A ... 

B B B B B B B B ... 

e voglio copiare ogni tre elementi di B-A per ottenere

B A A B A A B A ... 

Dal post "Is there a standard, strided version of memcpy?", sembra che non v'è alcuna possibilità di C.

Tuttavia, ho notato che, in alcuni casi, memcpy è più veloce di una copia basata su loop for.

La mia domanda è; Esiste un modo per eseguire efficientemente la copia della memoria a passi stretti in C++ eseguendo almeno come ciclo standard for?

Grazie mille.

EDIT - PRECISAZIONE DEL PROBLEMA

Per rendere più chiaro il problema, Indichiamo i due array a portata di mano da a e b. Ho una funzione che esegue la seguente unica for ciclo

for (int i=0; i<NumElements, i++) 
    a_[i] = b_[i]; 

dove entrambi i [] 's sono sovraccarichi operatori (sto usando una tecnica modelli di espressione), in modo che possano essere in realtà significa, per esempio

a[3*i]=b[i]; 
+0

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

+0

'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. –

+0

@dauphic Ma allora perché CUDA ha 'cudaMemcpy2D' che copia con intonazione? – JackOLantern

risposta

6
Is there any way to efficiently perform strided memory copy in C++ performing at least as a standard for loop? 

Edit 2: non esiste una funzione per la copia strided nelle librerie C++.

Dal momento che la copia striata non è una copia di memoria tanto popolare, i produttori di chip né i progetti linguistici hanno un supporto specializzato per la copia a grandi passi.

Supponendo un ciclo standard for, è possibile ottenere alcune prestazioni utilizzando Loop Unrolling. Alcuni compilatori hanno opzioni per srotolare i loop; non è un'opzione "standard".

Dato un standard difor ciclo:

#define RESULT_SIZE 72 
#define SIZE_A 48 
#define SIZE_B 24 

unsigned int A[SIZE_A]; 
unsigned int B[SIZE_B]; 
unsigned int result[RESULT_SIZE]; 

unsigned int index_a = 0; 
unsigned int index_b = 0; 
unsigned int index_result = 0; 
for (index_result = 0; index_result < RESULT_SIZE;) 
{ 
    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 
} 

Loop srotolando sarebbe ripetere il contenuto del "standard" for ciclo:

for (index_result = 0; index_result < RESULT_SIZE;) 
{ 
    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 

    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 
} 

Nella versione srotolato, il numero di i loop sono stati tagliati a metà.

Il miglioramento delle prestazioni può essere trascurabile rispetto ad altre opzioni. I seguenti problemi influire sulle prestazioni e ciascuna può avere diversi miglioramenti di velocità: cache di dati

  • Processing sbaglia
  • ricaricamento di pipeline (dipende dal processore)
  • sistema operativo scambiare memoria con disco
  • Altri compiti esecuzione contemporaneamente
  • elaborazione parallela (dipende processore/piattaforma)

Un esempio di elaborazione parallela è che un processore copia gli elementi B nel nuovo array e un altro processore copia gli elementi A nel nuovo array.

+0

Grazie per la tua gentile risposta. Ho modificato il mio post per spiegare meglio il problema. Pensi che con "#pragma srotolare" ho la possibilità di migliorare la situazione? Non lo so, dal momento che tutto ciò che riguarda la copia è noto in fase di esecuzione. – JackOLantern

+0

Come ho detto, dipende dal processore. Con alcuni processori, un ramo scarica la pipeline di istruzioni e il processore deve ricaricarlo. Alcuni processori moderni hanno abbastanza cache di pipeline di istruzioni che mantengono un semplice ciclo di loop nella cache delle istruzioni e non devono ricaricarsi. –

+0

La maggior parte dei processori preferisce eseguire istruzioni sequenziali e preferirebbe non incontrare le istruzioni di derivazione. –

6

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.