2009-07-15 9 views
50

Ci sono due modi per azzerare una matrice integer/float:compensazione una piccola matrice integer: vs memset ciclo

memset(array, 0, sizeof(int)*arraysize); 

o:

for (int i=0; i <arraysize; ++i) 
    array[i]=0; 

ovviamente, memset è più veloce di grande arraysize. Tuttavia, a che punto il sovraccarico di memset è effettivamente maggiore del sovraccarico del ciclo for? Ad esempio, per un array di dimensioni 5 - quale sarebbe il migliore? Il primo, il secondo, o forse anche il non-laminati versione:

array[0] = 0; 
array[1] = 0; 
array[2] = 0; 
array[3] = 0; 
array[4] = 0; 
+4

bene che non può veramente vedere la parte interessante. L'unica vera risposta sarà attraverso l'analisi comparativa delle tre versioni su una determinata piattaforma/compilatore. Inoltre, conoscere la risposta non è davvero utile, no? Ma allo scopo di scrivere in modo efficiente il codice benchmak;) – neuro

+0

sì ho intenzione di farlo da solo. ma ora tutto l'internet lo saprà! (almeno per windows con gcc da mingw ...) – Claudiu

+0

Mi chiedo come sei arrivato a quella domanda? –

risposta

41

Con ogni probabilità, memset() sarà inline dal compilatore (la maggior parte dei compilatori lo trattano come un 'intrinseca', che in pratica significa che è in linea, tranne forse con le ottimizzazioni più basse o se non esplicitamente disabilitato).

Ad esempio, qui alcuni release notes from GCC 4.3: generazione

codice di blocco movimento (memcpy) e del blocco (memset) stato riscritto. GCC può ora scegliere l'algoritmo migliore (loop, loop non terminato, istruzione con prefisso o una chiamata alla libreria in base alla dimensione del blocco da copiare e la CPU è ottimizzata per. Una nuova opzione -minline-stringops-dynamically è stata aggiunta . Con questa opzione stringa le operazioni di dimensioni sconosciute sono espanse in modo tale che i blocchi piccoli sono copiati dal codice in linea, mentre per i blocchi grandi viene utilizzata una chiamata di libreria. Questo risulta in un codice più veloce di -minline-all-stringops quando l'implementazione della libreria è in grado di utilizzando hint di gerarchia della cache. L'euristica che sceglie il particolare algoritmo può essere sovrascritta tramite -mstringop-strategy. Inoltre, anche memset di valori diversi da 0 è in linea.

Potrebbe essere possibile per il compilatore fare qualcosa di simile con gli esempi alternativi che hai dato, ma scommetto che è meno probabile.

Ed è grep -able e più immediatamente evidente a colpo d'occhio quale sia l'intenzione di avviare (non che il ciclo sia particolarmente difficile da eliminare).

+0

ottima risposta, grazie! – Claudiu

+9

Questo è un ottimo esempio del spesso sentito "il compilatore è migliore di te per l'ottimizzazione". Pochi programmatori di applicazioni impiegherebbero questa quantità di attenzione su una singola chiamata (e se lo facessero, probabilmente la loro applicazione effettiva ne risentirebbe). :) – unwind

+2

rilassarsi, se si pensa 'il compilatore è meglio di te' è qualcosa che si dovrebbe seguire, controllare questo fuori - http://www.liranuna.com/sse-intrinsics-optimizations-in-popular-compilers/ – LiraNuna

19

Come già detto, gcc e credo che molti altri compilatori lo ottimizzino già molto bene. Per esempio gcc trasforma questo

char arr[5]; 
memset(arr, 0, sizeof arr); 

in

movl $0x0, <arr+0x0> 
movb $0x0, <arr+0x4> 

Non c'è niente di meglio di così ...

+0

per gli array un po 'più grandi, i registri a 64 bit o SIMD possono essere utilizzati per azzerare 8/16/32 ... byte alla volta –

8

Non c'è modo di risolvere la questione senza misurare. Dipenderà interamente dalle implementazioni della libreria di compilazione, cpu e runtime.

memset() può essere un po 'un "odore di codice", perché può essere soggetto a buffer overflow, inversioni di parametri e ha la sfortunata capacità di cancellare solo' byte-saggio '. Tuttavia è una scommessa sicura che sarà "il più veloce" in tutti i casi tranne che estremi.

Io tendo ad usare una macro per avvolgere questo per evitare alcuni dei problemi:

#define CLEAR(s) memset(&(s), 0, sizeof(s)) 

Questo evita i calcoli di dimensioni e rimuove il problema di scambiare i parametri di lunghezza e vlaue.

In breve, utilizzare memset() "sotto il cofano". Scrivi ciò che intendi e lascia che il compilatore si preoccupi delle ottimizzazioni. La maggior parte sono incredibilmente bravi.

+0

++ Ohmygosh! U usi una macro !? Meglio andare a nascondersi! –

+1

Penso che hai fatto il parametro macro (x), ma usato (s) nel corpo reale ... potrebbe voler modificare quello. – micmoo

+0

@micmoo - grazie - risolto. @ mike re Macros: sì ... tuttavia sono inevitabili in C. La risposta C++ a questa domanda sarebbe * molto * diversa! – Roddy

1

Considerando che questo codice di per sé è già stato detto. Ma se lo consideri nel suo programma, di cui non so nulla, si può fare qualcos'altro. Ad esempio, se questo codice deve essere eseguito ogni volta per svuotare un array, è possibile eseguire un thread che assegna costantemente un nuovo array di elementi zero assegnato a una variabile globale, che il proprio codice, quando necessita dell'array da cancellare, punta semplicemente a.

Questa è una terza opzione. Naturalmente se si prevede di eseguire il codice su un processore con almeno due core e questo ha senso. Anche il codice deve essere eseguito più di una volta per vedere i benefici. Per una sola volta, è possibile dichiarare un array pieno di zeri e quindi puntarlo quando necessario.

Spero che questo possa aiutare qualcuno

+0

Questa è solo una buona idea per matrici abbastanza grandi. L'overhead di cache-miss dall'azzeramento su un core e l'utilizzo su un altro core, per non parlare dell'overhead di sincronizzazione, sarà un killer per i piccoli array. Immagino che un thread di azzeramento separato valga la pena di essere preso in considerazione per array più grandi di 16kiB. (La dimensione della cache L1 nelle moderne CPU Intel è 32kiB) –

+0

Liberare i vecchi array e allocare nuova memoria azzerata come suggerisci potrebbe essere più economica, soprattutto se la maggior parte delle pagine non verrà scritta. Su Linux, ad esempio, le pagine appena assegnate da mmap sono tutte mappate sulla stessa pagina fisica della memoria azzerata. 'calloc (3)' ne approfitta e non sporca le pagine (a differenza di 'std :: vector'). La prima scrittura su ogni pagina attiverà un errore di pagina soft. –

+0

Pensavo che ogni core avesse una propria cache L1, quindi non posso ottenere dove si verificherà la miss. Comunque potresti sapere più di me dei cache. L'overhead di sincronizzazione non dovrebbe essere un problema se l'array non viene cancellato troppo spesso. Mentre il thread di clearing tiene il lock, l'altro thread farebbe qualcos'altro. Il vero svantaggio è che devi sapere molto bene quanto spesso l'array deve essere cancellato nel thread "principale", il che non è sempre possibile, immagino. Se non è possibile, la mia idea non funzionerà e rallenterà molto tutto – fedeb