2010-07-09 12 views
11

Esiste una funzione (SSEx intrinsics è OK) che riempirà la memoria con un valore specificato int32_t? Per esempio, quando questo valore è pari a 0xAABBCC00 memoria risultato dovrebbe assomigliare:Come riempire velocemente la memoria con un valore `int32_t`?

AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00 
... 

potrei usare std::fill o semplice per-loop, ma non è abbastanza veloce.


Ridimensionamento di un vettore eseguito solo una volta all'inizio del programma, ciò non è un problema. Il collo di bottiglia sta riempiendo la memoria.

codice semplificato:

struct X 
{ 
    typedef std::vector<int32_t> int_vec_t; 
    int_vec_t buffer; 

    X() : buffer(5000000) { /* some more action */ } 
    ~X() { /* some code here */ } 

    // the following function is called 25 times per second 
    const int_vec_t& process(int32_t background, const SOME_DATA& data); 
}; 

const X::int_vec_t& X::process(int32_t background, const SOME_DATA& data) 
{ 
    // the following one string takes 30% of total time of #process function 
    std::fill(buffer.begin(), buffer.end(), background); 

    // some processing 
    // ... 

    return buffer; 
} 
+1

Perché non lo si codifica da soli utilizzando le istruzioni SSE? Hai le istruzioni movxxxx per spostare la memoria (128 bit alla volta). È solo un ciclo e si muove, non dovrebbe essere difficile da fare. –

+1

Devo ammettere che sono un po 'curioso di sapere quale potrebbe essere il tuo caso d'uso per questo che rende un ciclo for non abbastanza veloce. Hai appena a che fare con un tremendo blocco di memoria? La tua piattaforma fa schifo alle filiali? Lo fai spesso in un'app ad alte prestazioni, come un gioco o qualcosa del genere? –

+0

@Alexandre C., le istruzioni SSE sono OK, ma ho pensato che forse c'è già qualche funzione in WinAPI. Non voglio inventare una ruota. –

risposta

4

Grazie a tutti per le vostre risposte. Ho controllato wj32's solution, ma mostra molto tempo simile a std::fill do. La mia soluzione attuale funziona 4 volte più veloce (in Visual Studio 2008) di std::fill con l'aiuto della funzione memcpy:

// fill the first quarter by the usual way 
std::fill(buffer.begin(), buffer.begin() + buffer.size()/4, background); 
// copy the first quarter to the second (very fast) 
memcpy(&buffer[buffer.size()/4], &buffer[0], buffer.size()/4*sizeof(background)); 
// copy the first half to the second (very fast) 
memcpy(&buffer[buffer.size()/2], &buffer[0], buffer.size()/2*sizeof(background)); 

Nel codice di produzione si ha la necessità di aggiungere controllo se buffer.size() è divisibile per 4 e aggiungere la gestione appropriato per tale .

9

Ecco come lo farei (scusate il Microsoft-ness di esso):

VOID FillInt32(__out PLONG M, __in LONG Fill, __in ULONG Count) 
{ 
    __m128i f; 

    // Fix mis-alignment. 
    if ((ULONG_PTR)M & 0xf) 
    { 
     switch ((ULONG_PTR)M & 0xf) 
     { 
      case 0x4: if (Count >= 1) { *M++ = Fill; Count--; } 
      case 0x8: if (Count >= 1) { *M++ = Fill; Count--; } 
      case 0xc: if (Count >= 1) { *M++ = Fill; Count--; } 
     } 
    } 

    f.m128i_i32[0] = Fill; 
    f.m128i_i32[1] = Fill; 
    f.m128i_i32[2] = Fill; 
    f.m128i_i32[3] = Fill; 

    while (Count >= 4) 
    { 
     _mm_store_si128((__m128i *)M, f); 
     M += 4; 
     Count -= 4; 
    } 

    // Fill remaining LONGs. 
    switch (Count & 0x3) 
    { 
     case 0x3: *M++ = Fill; 
     case 0x2: *M++ = Fill; 
     case 0x1: *M++ = Fill; 
    } 
} 
+2

Sarei interessato a come confrontare le prestazioni con std :: fill. –

+6

Scusate, sono un ragazzo semplice. Non so nulla di std, fill o std :: fill. – wj32

+0

in modo da utilizzare istruzioni SSE intrinseche ... ma ciò viene fatto automaticamente da qualsiasi compilatore decente, ad es. gcc o icpc per te quando scrivi il ciclo di vaniglia. Quindi penso che non ce ne sia bisogno. –

5

devo chiedere: avete sicuramente profilato std::fill e mostrato che si tratta del collo di bottiglia delle prestazioni? Immagino che sia implementato in modo abbastanza efficiente, in modo che il compilatore possa generare automaticamente le istruzioni appropriate (ad esempio -march su gcc).

Se è il collo di bottiglia, può essere ancora possibile ottenere una migliore beneficio da una riprogettazione algoritmico (se possibile) per evitare di impostare la memoria così tanto (a quanto pare più e più volte) in modo che essa non ha più importanza che riempiono meccanismo usate.

3

Hai pensato di usare

vector<int32_t> myVector; 
myVector.reserve(sizeIWant); 

e quindi utilizzare std :: riempire? O forse il costruttore di un std::vector che prende come argomento il numero di elementi detenuti e il valore per inizializzarli in?

+0

Questo è davvero un buon punto. Se stai aggiungendo un vettore, parte del tuo sovraccarico potrebbe essere nel ridimensionare quel vettore, cosa che non succederebbe ogni volta che hai inserito fino alla fine (il vettore si espande automaticamente un po 'più grande del necessario), ma sarebbe essere abbastanza spesso da incorrere in un colpo di prestazioni. Usa reserve() per pre-allocare una certa lunghezza. –

+1

Puoi anche usare un array per essere sicuro al 100% che il ridimensionamento non sia il problema. –

+0

Sì, originariamente pensavo stesse provando a impostare i valori in un array che ha creato con malloc(). Non ho nemmeno pensato che potrebbe essere ridimensionamento del vettore rallentandolo. :) –

0

Non completamente sicuro come si impostano 4 byte in una riga, ma se si desidera riempire la memoria con un solo byte su un altro, è possibile utilizzare memset.

void * memset (void * ptr, int value, size_t num); 

blocco di riempimento della memoria

Imposta i primi num byte del blocco di memoria puntata dal ptr al valore specificato (interpretato come unsigned char).

+2

Non voglio riempire la memoria con un byte. Ci sono quattro byte in 'int32_t'. –

0

Supponendo di avere una quantità limitata di valori nel parametro di sfondo (o anche meglio, solo in), forse dovresti provare ad allocare un vettore statico e semplicemente usare memcpy.

const int32_t sBackground = 1234; 
static vector <int32_t> sInitalizedBuffer(n, sBackground); 

    const X::int_vec_t& X::process(const SOME_DATA& data) 
    { 
     // the following one string takes 30% of total time of #process function 
     std::memcpy((void*) data[0], (void*) sInitalizedBuffer[0], n * sizeof(sBackground)); 

     // some processing 
     // ... 

     return buffer; 
    } 
-2

Potrebbe essere un po 'non portatile ma è possibile utilizzare una copia di memoria sovrapposta. Riempi i primi quattro byte con lo schema che desideri e usa memcpy().

int32* p = (int32*) malloc(size); 
*p = 1234; 
memcpy(p + 4, p, size - 4); 

Non credo che si può ottenere molto più veloce std

+0

Non è supportato. vedere http://stackoverflow.com/questions/387654/why-is-there-no-z80-like-ldir-functionality-in-c-c-rtl – EvilTeach

+0

Potrebbe non essere "supportato" ma funziona in vs2008. Posso fornire la fonte se necessario. Inoltre, non riesco a trovare ciò a cui ti stai riferendo nella pagina collegata. – Jay

+1

La sovrapposizione di memcpy è un bug noto e raccomandarli è un cattivo consiglio. memmove è la chiamata corretta da utilizzare per le regioni sovrapposte. "Non è supportato ma funziona" è per l'incisione su pietre tombali. –

0

Ho appena testato :: riempire con g ++ con le ottimizzazioni pieno (SSE ecc ..abilitato):

#include <algorithm> 
#include <inttypes.h> 

int32_t a[5000000]; 

int main(int argc,char *argv[]) 
{ 
    std::fill(a,a+5000000,0xAABBCC00); 
    return a[3]; 
} 

e il ciclo interno sembrava:

L2: 
    movdqa %xmm0, -16(%eax) 
    addl $16, %eax 
    cmpl %edx, %eax 
    jne L2 

Sembra 0xAABBCC00 x 4 è stato caricato in xmm0 e viene spostato 16 byte alla volta.

+0

Sono curioso, perché questo codice che usa confronti e salti condizionati ancora più veloci di 'REPNZ STOS' o simili? – Philipp

+0

@Philipp: copia 16 byte alla volta. I confronti e i salti condizionali non sono necessariamente costosi. Dipende molto dal contesto, quali altre istruzioni vengono eseguite. – jalf

0

vs2013 e vs2015 possono ottimizzare un'istruzione for-loop semplice a rep stos. È il modo più veloce per riempire un buffer. È possibile specificare il std::fill per il vostro tipo come questo:

namespace std { 
    inline void fill(vector<int>::iterator first, vector<int>::iterator last, int value){ 
     for (size_t i = 0; i < last - first; i++) 
      first[i] = value; 
    } 
} 

BTW. Per fare in modo che il compilatore esegua l'ottimizzazione, è necessario accedere al buffer dall'operatore di pedici.

Non funzionerà su gcc e clang. Entrambi compileranno il codice in un ciclo di salto condizionato. Funziona lento come l'originale std::fill. E sebbene lo wchar_t sia a 32 bit, lo wmemset non ha un attrezzo di assemblaggio come lo memset. Quindi devi scrivere codice assemblare per fare l'ottimizzazione.

Problemi correlati