2016-02-10 25 views
5

So per tutorial che il carico non allineato e la memorizzazione può apparire come:Come conservare un vettore in una posizione non allineata in memoria con Altivec

//Load a vector from an unaligned location in memory 
__vector unsigned char LoadUnaligned(const unsigned char * src) 
{ 
    __vector unsigned char permuteVector = vec_lvsl(0, src); 
    __vector unsigned char low = vec_ld(0, src); 
    __vector unsigned char high = vec_ld(16, src); 
    return vec_perm(low, high, permuteVector); 
} 

//Store a vector to an unaligned location in memory 
void StoreUnaligned(__vector unsigned char v, __vector unsigned char * dst) 
{ 
    //Load the surrounding area 
    __vector unsigned char low = vec_ld(0, dst); 
    __vector unsigned char high = vec_ld(16, dst); 
    //Prepare the constants that we need 
    __vector unsigned char permuteVector = vec_lvsr(0, (int*) dst); 
    __vector signed char oxFF = vec_splat_s8(-1); 
    __vector signed char ox00 = vec_splat_s8(0); 
    //Make a mask for which parts of the vectors to swap out 
    __vector unsigned char mask = vec_perm(ox00, oxFF, permuteVector); 
    //Right rotate our input data 
    v = vec_perm(v, v, permuteVector); 
    //Insert our data into the low and high vectors 
    low = vec_sel(v, low, mask); 
    high = vec_sel(high, v, mask); 
    //Store the two aligned result vectors 
    vec_st(low, 0, dst); 
    vec_st(high, 16, dst); 
} 

Sembra terribile. Così grande quantità di lavoro al fine di memorizzare un vettore! E ha una perdita di prestazioni appropriata.

void SomeFuncA(const unsigned char * src, size_t size, unsigned char * dst) 
{ 
    for(size_t i = 0; i < size; i += 16) 
    { 
     __vector unsigned char a = vec_ld(0, src + i); 
     //simple work 
     vec_st(a, 0, dst + i); 
    }  
} 

void SomeFuncU(const unsigned char * src, size_t size, unsigned char * dst) 
{ 
    for(size_t i = 0; i < size; i += 16) 
    { 
     __vector unsigned char a = LoadUnaligned(src + i); 
     //simple work 
     StoreUnaligned(dst + i, a); 
    }  
} 

La seconda funzione funziona in 3-4 volte lentamente rispetto alla prima. Poiché non riesco a controllare l'allineamento della memoria di input e output, devo implementare entrambe le versioni. Come posso ridurre al minimo la perdita di prestazioni per caso non allineato?

risposta

4

Prima di tutto voglio dire che se si salva il vettore Altivec in una memoria non allineata molte volte non è necessario salvare lo stato della memoria precedente nel mezzo della matrice solo all'inizio e alla fine. Quindi c'è una funzione utile e classe nel Simd Library, che implementare questa funzionalità:

typedef __vector uint8_t v128_u8; 
const v128_u8 K8_00 = vec_splat_u8(0x00); 
const v128_u8 K8_FF = vec_splat_u8(0xFF); 

template <bool align> inline v128_u8 Load(const uint8_t * p); 

template <> inline v128_u8 Load<false>(const uint8_t * p) 
{ 
    v128_u8 lo = vec_ld(0, p); 
    v128_u8 hi = vec_ld(16, p); 
    return vec_perm(lo, hi, vec_lvsl(0, p));   
}   

template <> inline v128_u8 Load<true>(const uint8_t * p) 
{ 
    return vec_ld(0, p); 
} 

template <bool align> struct Storer; 

template <> struct Storer<true> 
{ 
    template <class T> Storer(T * ptr) 
     :_ptr((uint8_t*)ptr) 
    { 
    } 

    template <class T> inline void First(T value) 
    { 
     vec_st((v128_u8)value, 0, _ptr); 
    } 

    template <class T> inline void Next(T value) 
    { 
     _ptr += 16; 
     vec_st((v128_u8)value, 0, _ptr); 
    } 

    inline void Flush() 
    { 
    } 
private: 
    uint8_t * _ptr; 
}; 

template <> struct Storer<false> 
{ 
    template <class T> inline Storer(T * ptr) 
     :_ptr((uint8_t*)ptr) 
    { 
     _perm = vec_lvsr(0, _ptr); 
     _mask = vec_perm(K8_00, K8_FF, _perm); 
    } 

    template <class T> inline void First(T value) 
    { 
     _last = (v128_u8)value; 
     v128_u8 background = vec_ld(0, _ptr); 
     v128_u8 foreground = vec_perm(_last, _last, _perm); 
     vec_st(vec_sel(background, foreground, _mask), 0, _ptr); 
    } 

    template <class T> inline void Next(T value) 
    { 
     _ptr += 16; 
     vec_st(vec_perm(_last, (v128_u8)value, _perm), 0, _ptr); 
     _last = (v128_u8)value; 
    } 

    inline void Flush() 
    { 
     v128_u8 background = vec_ld(16, _ptr); 
     v128_u8 foreground = vec_perm(_last, _last, _perm); 
     vec_st(vec_sel(foreground, background, _mask), 16, _ptr); 
    } 
private: 
    uint8_t * _ptr; 
    v128_u8 _perm; 
    v128_u8 _mask; 
    v128_u8 _last; 
}; 

Il suo utilizzo sarà simile:

template<bool align> void SomeFunc(const unsigned char * src, size_t size, unsigned char * dst) 
{ 
    Storer<align> _dst(dst); 
    __vector unsigned char a = Load<align>(src); 
    //simple work 
    _dst.First(a);// save first block 
    for(size_t i = 16; i < size; i += 16) 
    { 
     __vector unsigned char a = Load<align>(src + i); 
     //simple work 
     _dst.Next(a);// save body 
    } 
    _dst.Flush(); // save tail  
} 

perdita di performance sarà 30-40% confronta con allineato versione. È spiacevolmente ovviamente, ma tollerante.

Ulteriore vantaggio è la riduzione del codice: tutte le funzioni (allineate e non allineate) hanno la stessa implementazione.

+0

Grazie! Sembra grandioso! – Georg