Как сохранить вектор в невыровненном месте в памяти с помощью 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);
}

Это выглядит ужасно. Так много работы, чтобы сохранить один вектор!
И имеет соответствующую потерю производительности.

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);
}
}

Вторая функция работает в 3-4 раза медленнее, чем первая.
Так как я не могу управлять выравниванием входной и выходной памяти, я должен реализовать обе версии.
Как я могу минимизировать потерю производительности для невыровненного случая?

5

Решение

Прежде всего я хочу отметить, что если вы много раз сохраняете вектор Altivec в невыровненной памяти, вам не нужно сохранять предыдущее состояние памяти в середине массива только в начале и в конце.
Так что есть полезная функция и класс в Библиотека Simd, которые реализуют эту функциональность:

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;
};

Его использование будет выглядеть так:

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
}

Потеря производительности составит 30-40% по сравнению с согласованной версией.
Это неприятно конечно но терпимо.

Дополнительным преимуществом является сокращение кода — все функции (выровненные и не выровненные) имеют одинаковую реализацию.

4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]