2013-03-18 13 views
7

Sto cercando di imparare le funi delle intrinseche SSE in C. Ho un pezzo di codice in cui carico un vettore a due componenti di dati doppi, aggiungo qualcosa e poi cerco di memorizzarlo torna alla memoria. Tutto funziona: posso caricare i miei dati nei registri SEE, posso operare sui miei dati in quei registri SSE, ma nel momento in cui provo a scrivere i dati elaborati nell'array originale (che è il punto in cui ho letto i miei dati da primo posto!) Ottengo un errore di segmentazione.SSE _mm_load_pd funziona mentre _mm_store_pd segfaults

Qualcuno può consigliarmi su questo argomento - questo mi sta facendo impazzire.

double res[2] __attribute__((aligned(16))); 

for(int k=0; k<n; k++){ 
int i=0; 
for(; i+1<n; i+=2) 
    { 
    __m128d cik = _mm_load_pd(&C[i+k*n]); 
    int j = 0; 
    for(; j+1<n; j+=2) 
     { 
     __m128d aTij = _mm_load_pd(&A_T[j+i*n]); 
     __m128d bjk = _mm_load_pd(&B[j+k*n]); 
     __m128d dotpr = _mm_dp_pd(aTij, bjk,2); 
     cik = _mm_add_pd(cik, dotpr); 
     } 
    _mm_store_pd(res, cik); 
    //C[i+k*n] = res[0]; 
    } 
} 

Come ho già detto in precedenza, tutto funziona in questo codice eccezione di dove posso conservare i miei risultati indietro a quella matrice unidimensionale "C" dove ho letto i miei dati da in primo luogo. Cioè, quando ho rimuovere il commento segni di fronte

//C[i+k*n] = res[0]; 

ottengo un segmentation fault.

Come è possibile che io possa leggere da C con la versione di memoria allineata di _mm_load_pd (quindi C deve essere allineato in memoria!) Mentre la scrittura su di esso non funziona? "C" deve essere allineato e, come puoi vedere, "res" deve essere allineato.

responsabilità: mio codice originale leggere

_mm_store_pd(&C[i+k*n], cik); 

che ha prodotto anche un errore di segmentazione e ho iniziato l'introduzione di "res" con l'allineamento esplicito nel mio tentativo di risolvere il problema.

Addendum

A, B, C sono dichiarati come segue:

buf = (double*) malloc (3 * nmax * nmax * sizeof(double)); 
double* A = buf + 0; 
double* B = A + nmax*nmax; 
double* C = B + nmax*nmax; 

tentata soluzione con posix_memalign

Nel tentativo di risolvere il problema di errore di segmentazione quando si scrive al array monodimensionale originale, ora utilizzo i buffer per le matrici corrispondenti. Tuttavia, questo seg segna ancora quando si tenta di riscrivere su C_buff!

double res[2] __attribute__((aligned(16))); 

double * A_T; 
posix_memalign((void**)&A_T, 16, n*n*sizeof(double)); 

double * B_buff; 
posix_memalign((void**)&B_buff, 16, n*n*sizeof(double)); 

double * C_buff; 
posix_memalign((void**)&C_buff, 16, n*n*sizeof(double)); 

for(int y=0; y<n; y++) 
    for(int x=0; x<n; x++) 
    A_T[x+y*n] = A[y+x*n]; 

for(int x=0; x<n; x++) 
    for(int y=0; y<n; y++) 
    B_buff[y+x*n] = B[y+x*n]; 

for(int x=0; x<n; x++) 
    for(int y=0; y<n; y++) 
    C_buff[y+x*n] = C[y+x*n]; 

for(int k=0; k<n; k++){ 
    int i=0; 
    for(; i+1<n; i+=2) 
    { 
     __m128d cik = _mm_load_pd(&C_buff[i+k*n]); 
     int j = 0; 
     for(; j+1<n; j+=2) 
     { 
      __m128d aTij = _mm_load_pd(&A_T[j+i*n]); 
      __m128d bjk = _mm_load_pd(&B_buff[j+k*n]); 
      __m128d dotpr = _mm_dp_pd(aTij, bjk,2); 
      cik = _mm_add_pd(cik, dotpr); 
     } 
     _mm_store_pd(&C_buff[i+k*n], cik); 

    //_mm_store_pd(res, cik); 
     //C_buff[i+k*n] = res[0]; 
    //C_buff[i+1+k*n] = res[1]; 
    } 
} 
+1

Come viene dichiarato 'C'? –

+0

@TonyTheLion si prega di consultare l'addendum in questione. Per quanto ho capito, malloc tenta di allineare il pezzo di memoria che alloca ma non sempre riesce a tutti gli effetti. Il mio principale punto di confusione su quanto sopra è che posso leggere da quella particolare posizione in "C" ma non riesco a scriverlo. Quindi "C" appare allineata allo scopo di leggere ma non scrivere? –

+0

Penso che partendo dal presupposto che 'malloc' allineerà qualcosa è incerto, potresti voler usare [' aligned_alloc'] (http://man7.org/linux/man-pages/man3/posix_memalign.3.html) se stai usando GCC o ['_aligned_malloc'] (http://msdn.microsoft.com/en-us/library/8z34s9c6%28VS.80%29.aspx) se stai usando MSVC. –

risposta

0

Anche con l'__attribute__((aligned(32))), mi è stato sempre lo stesso errore (aveva 50% possibilità di mis-alingment). Poi ho usato la seguente funzione per ottenere un% 100 possibilità di allineamento (una dovrebbe essere potenza di due):

void * malloc_float_align(size_t n, unsigned int a/*alignment*/, float *& output) 
{ 
    void * adres=NULL; 
    void * adres2=NULL; 
    adres=malloc(n*sizeof(float)+a); 
    size_t adr=(size_t)adres; 
    size_t adr2=adr+a-(adr&(a-1u)); // a valid address for a alignment 
    adres2=(void *) adr2; 
    output=(float *)adres2; 
    return adres;    //pointer to be used in free() 
} 

Poi l'utilizzo in principale:

int main() 
{ 


    float * res=NULL; 
    void * origin=malloc_float_align(1024,32u,res); 
    //use res for sse/avx 
    free(origin); // actual allocation is more than 1024 elements 
    return 0; 
} 

Naturalmente questo è in C++ in modo da poter fallo funzionare solo cambiando lo stile di alcuni parametri di funzione.

+0

Nota su GCC/clang c'è memalign e su VC++ c'è _aligned_malloc –

+0

Sembra che stiano lavorando in modo simile ma liberando l'inutile frammento nel frattempo che non potrei farlo all'interno della funzione perché non so come dire al compilatore la nuova dimensione da liberare. –

+0

Puoi chiamare gratuitamente sui risultati di memalign e _aligned_malloc. –

0

un semplice trucco sarebbe quello di eseguire un affermare e vedere se si innesca:

ASSERT(((size_t)(&C_buff[i+k*n]) & 0xF) == 0); 

l'asserzione viene emesso quando l'indirizzo non è SSE allineato. I build a 64 bit dovrebbero fornire l'allineamento 16B per impostazione predefinita. Se si intende avere un codice a 32 bit, utilizzare una delle funzioni align_malloc di cui sopra. Avrai bisogno di usare il align_free pertinente o andrai in crash.

1

Quando si rimuove lo _mm_store_pd(&C_buff[i+k*n], cik);, l'intero ciclo viene ottimizzato e rimosso. Il compilatore deduce che l'intero ciclo for non porta a nessun lavoro significativo e lo rimuove. Questo è il motivo per cui non ottieni più il problema di segmentazione.
Sono sicuro che l'errore di segmentazione è dovuto alla dimensione dell'array. Considerate questo semplice programma basato sul tuo esempio:

#include <stdio.h> 
#include "emmintrin.h" 

int main(){ 
int n = 15; 
int y,x,k,i,j; 

double * A; 
posix_memalign((void**)&A, 16, n*n*sizeof(double)); 

double * B; 
posix_memalign((void**)&B, 16, n*n*sizeof(double)); 

double * C; 
posix_memalign((void**)&C, 16, n*n*sizeof(double)); 

for(y=0; y<n; y++) 
    for(x=0; x<n; x++) 
    A[x+y*n] = 0.1; 

for(x=0; x<n; x++) 
    for(y=0; y<n; y++) 
    B[y+x*n] = 0.1; 

for(x=0; x<n; x++) 
    for(y=0; y<n; y++) 
    C[y+x*n] = 0.1; 

for(k=0; k<n; k++){ 
    i=0; 
    for(; i+1<n; i+=2) 
    { 
     __m128d cik = _mm_load_pd(&C[i+k*n]); 
     j = 0; 
     for(; j+1<n; j+=2) 
     { 
      __m128d aTij = _mm_load_pd(&A[j+i*n]); 
      __m128d bjk = _mm_load_pd(&B[j+k*n]); 
      __m128d dotpr = _mm_add_pd(aTij, bjk); 
      cik = _mm_add_pd(cik, dotpr); 
     } 
     _mm_store_pd(&C[i+k*n], cik); 
    } 
} 
printf("C[15]: %f\n", C[15]); 
printf("C[14]: %f\n", C[14]); 

Questo dà un errore di segmentazione, perché n è dispari. Ora cambia da n = 15 a n = 16 e tutto verrà eseguito come previsto. Quindi, il riempimento degli array su un numero pari (o anche meglio su una linea cache -> 64 byte == 8 elementi DP o 16 elementi SP) impedirà tali problemi e porterà a prestazioni migliori.