2015-07-15 13 views
5

Questa è una sorta di follow-up di questa domanda originale con alcune nuove informazioni aggiunte. Vedi qui per la prima parte se sei interessato: Struct of arrays, arrays of structs and memory usage patternStrutturazione di array e modelli di accesso alla memoria

Sembra che ci siano alcuni problemi con il mio primo tentativo di impostare la struttura degli array per una classe semplice. Essenzialmente si tratta di allocazione di memoria in eccesso per i puntatori e possibili perdite di memoria dall'allocazione di quei puntatori da vec3_b nella domanda precedente.

Ho pensato a come riordinare i dati senza usare i puntatori, questo mi impone di impostare prima alcune variabili const per la dimensione dei miei bucket di dati, quindi nessun valore illimitato come puntatori ma riduce anche la quantità di memoria a qualcosa di fisso.

const size_t batch_size = 100; 
    struct vec3_c 
    { 
    size_t x[batch_size]; 
    size_t y[batch_size]; 
    size_t z[batch_size]; 
    }; 

    struct vec3_c vec3_c(size_t x, size_t y, size_t z, size_t index) 
    { 
     struct vec3_c v; 
     v.x[index] = x; 
     v.y[index] = y; 
     v.z[index] = z; 
     return v; 
    } 

     struct vec3_c vc3;   
     for(int i = 0; i < batch_size; i++) 
     { 
      vc3 = vec3_c(i+1, i*i, i*10, i); 
      //printf("vec3c x:%zu, y:%zu, z:%zu\n",vc3.x[i], vc3.y[i], vc3.z[i]); 
      printf("vec3c x:%p, y:%p, z:%p\n",(void*)&vc3.x[i], (void*)&vc3.y[i], (void*)&vc3.z[i]); 
     } 

     ---------------x-----------------|----------------y-----------------|----------------z-----------------| 

0|  0x7fff57489f40 : 140734657765184 | 0x7fff5748a260 : 140734657765984 | 0x7fff5748a580 : 140734657766784 
1|  0x7fff57489f48 : 140734657765192 | 0x7fff5748a268 : 140734657765992 | 0x7fff5748a588 : 140734657766792 
2|  0x7fff57489f50 : 140734657765200 | 0x7fff5748a270 : 140734657766000 | 0x7fff5748a590 : 140734657766800 

con questo codice aggiornato devo avere una dimensione fissa secchio così mi sono messo a batch_size di 100 solo per i numeri semplici. Riempi il vec3c con alcuni dati e fai un test simile, questa volta sembra che ogni valore sia allineato in blocchi da 8 byte.

es:

size of vec3  : 24 bytes 
size of vec3a  : 24 bytes 
size of vec3b  : 24 bytes 
size of vec3c  : 2400 bytes 
size of size_t : 8 bytes 
size of int  : 4 bytes 
size of 16 int : 64 bytes 
vec3c x:0x7fff592d2f40, y:0x7fff592d3260, z:0x7fff592d3580 
vec3c x:0x7fff592d2f48, y:0x7fff592d3268, z:0x7fff592d3588 
vec3c x:0x7fff592d2f50, y:0x7fff592d3270, z:0x7fff592d3590 
vec3c x:0x7fff592d2f58, y:0x7fff592d3278, z:0x7fff592d3598 
vec3c x:0x7fff592d2f60, y:0x7fff592d3280, z:0x7fff592d35a0 
vec3c x:0x7fff592d2f68, y:0x7fff592d3288, z:0x7fff592d35a8 
vec3c x:0x7fff592d2f70, y:0x7fff592d3290, z:0x7fff592d35b0 
vec3c x:0x7fff592d2f78, y:0x7fff592d3298, z:0x7fff592d35b8 
vec3c x:0x7fff592d2f80, y:0x7fff592d32a0, z:0x7fff592d35c0 
vec3c x:0x7fff592d2f88, y:0x7fff592d32a8, z:0x7fff592d35c8 

sono separate da 8 byte.

Questo dovrebbe eliminare i problemi di perdite di memoria e la memoria in eccesso necessaria per i puntatori.

con questo essendo il nuovo layout qualcosa come sizeof (vc3 [0] .x) restituirebbe 8 byte.

nuovo alle domande originali:

  1. È il mio attuazione struct vec3_c il modo corretto di impostare una struttura di array?

  2. con una dimensione di batch vec_3c di 100 mostra 2400 byte di larghezza ma ogni singolo elemento è solo 8 byte e allineato correttamente, quindi ora posso inserire 8 elementi su 1 linea di cache cpu moderna?

  3. trasformerebbe i dati passati a me in un formato tipico di soli array di strutture superiori ai benefici di prestazioni di essere in uno stato di cache friendly e in grado di operare su più punti di dati per chiamata di istruzioni? Questo è con l'avvertenza che entrambi i punti 1 e 2 sono corretti.

ex facendo il prodotto scalare di due vettori: che significa che ho potuto ottenere il prodotto scalare di 2 vec3_c per ciclo di istruzione?

modificare ancora una domanda, sarebbe meglio per aggiungere le ulteriori 8 byte di dati per rendere questa struct è un multiplo di 32 byte e magari utilizzare tale ulteriore 8 byte come spazio zero o semplicemente lasciare vuoto?

modifica Mi è stato fatto notare che la mia funzione di inizializzazione iniziale stava solo facendo un casino di cose.Ho aggiornato a questa forma:

struct vec3_c* vec3_c() 
{ 
    struct vec3_c *v = (struct vec3_c*)malloc(sizeof(struct vec3_c)); 
    v->index = 0; 
    return v; 
} 

struct vec3_c* v3 = vec3_c(); 
    for(size_t i = 0; i < batch_size; i++) 
    { 
     v3->x[i] = i + 1; 
     v3->y[i] = i * i; 
     v3->z[i] = i * 10; 
     printf("index:%d\tvec3c x:%zu, y:%zu, z:%zu\n",i,v3->x[i], v3->y[i], v3->z[i]); 
     printf("index:%zu\tvec3c x:%p, y:%p, z:%p\n",i,(void*)&v3->x[i], (void*)&v3->y[i], (void*)&v3->z[i]); 
    } 
+2

Spiacente, forse sto interpretando erroneamente l'intero problema, ma mi sembra che si stia utilizzando vc3 an v in modo errato: infatti, la funzione vec3_c alloca una "v" locale (non inizializzata), quindi imposta SOME campi e restituisce una COPIA OF v (i cui campi contengono valori imprevedibili, ad eccezione di "index"). Vedo che nel tuo esempio non sei realmente interessato al contenuto di vc3, quindi qual è lo scopo della funzione vec3_c? –

+0

@GiuseppeGuerrini il mio esempio di codice iniziale aveva alcuni bug che ho risolto nel tempo. Vorrei usare questa struttura per elaborare in batch i valori di vec3 qualcosa come un'elaborazione del flusso o istruzioni di tipo SIMD. – user1610950

risposta

3

Se avete intenzione di avere sacco di questi punti xyz e si vuole essere in grado di eseguire un'azione su tutte le x in una sola volta, allora ha più senso mettere tutte le x insieme:

struct PointBatch { 
    size_t x[batchsize]; 
    size_t y[batchsize]; 
    size_t z[batchsize]; 
} 

// More efficient for things like 
// - find the point with the largest X 
// - find the sum of all the points as [xsum, ysum, zsum] 

Se normalmente operano sul x, y, z di punti dati singoli, allora più senso mettere ogni punto insieme come struct.

struct Point { 
    size_t x; 
    size_t y; 
    size_t z; 
} 

struct Point pointBatch[batchsize]; 

// Better for things like 
// - plot all the points on a graph 
// - determine which points satisfy the equation: x^2 + y^2 < z^2 

N.B.
noti che dove le prestazioni sono non un problema, probabilmente troverete che l'approccio Point/pointBatch rende il codice più facile da scrivere e più leggibile come struct PointBatch ti dà alcun modo conveniente per fare riferimento o passare intorno ad un singolo punto.

+0

molto probabilmente i dati mi saranno passati sotto forma di struct A ma per elaborarlo in modo più efficiente nei miei algoritmi dovrò trasformarmi da A a B una volta e immagazzinare B per elaborarli in batch. Ho fatto riferimento a questo nella domanda iniziale chiedendo il costo della trasformazione una volta in anticipo rispetto al batching per la coerenza della cache e l'elaborazione dello stile SIMD. – user1610950

+0

In realtà, se è necessario eseguire tutti i punti in sequenza, una struct-of-array è MOLTO meglio per SIMD. L'esempio di 'x^2 + y^2

+0

grazie che è la direzione verso cui mi sto dirigendo per eseguire l'elaborazione batch dei dati di tipo SIMD. Dovrò prima ruotarli da AoS a SoA. – user1610950

2

C'è almeno un problema con questa parte del codice:

struct vec3_c vec3_c(size_t x, size_t y, size_t z, size_t index) 
{ 
    struct vec3_c v; 
    v.x[index] = x; 
    v.y[index] = y; 
    v.z[index] = z; 
    return v; 
} 

Si crea un nuovo vec3_v struct sullo stack e restituirlo. Ciò significa che ogni chiamata restituirà una nuova struttura con tutti gli elementi non inizializzati tranne la riga index.

E in seguito quando si esegue (100 volte) vc3 = vec3_c(i+1, i*i, i*10, i);, si copiano 300 valori (dimensione della struttura), 297 dei quali non sono inizializzati: ciò è davvero inefficiente e richiama il comportamento non definito!

+0

In realtà è il chiamante che alloca lo spazio per il valore restituito. Negli ABI x86 e amd64, il chiamante passa un puntatore a questo spazio. (E il callee dovrebbe eseguire il costruttore, se non fosse già il costruttore, quindi sì, dovrebbe azzerare le parti che non ha scritto, o sarebbe? Il codice non usa un inizializzatore che è necessario azzerare gli elementi non modificati. Il 'vec3_c' restituito non è inizializzato ad eccezione di' v. * [indice] ') Se si è fortunati, il compilatore potrebbe eliminare la maggior parte di ciò dopo aver integrato tale costruttore. –

+0

@PeterCordes: certo che sei vero! È C e non C++ ... Ed è esplicito in 6.7.9 10: * Se un oggetto che ha durata di archiviazione automatica non è inizializzato in modo esplicito, il suo valore è indeterminato * –

+0

Anche se fosse C++, senza zero-arg costruttore, non sarebbe ancora non inizializzato? –