尝试使用 x86 asm SSSE3 将大端转换为小端序



我已经做了一段时间的arm asm,并尝试使用x86 asm ssse3优化简单的循环。我找不到将大端序转换为小端序的方法。

ARM NEON 只有一个向量指令来做到这一点,但 SSSE3 没有。我尝试使用 2 个移位和一个 or 但如果我们向左移动 8,则需要每个插槽达到 32 位而不是 16 位(数据饱和(。

我查看了 PSHUFB,但当我使用它时,16 位字的前半部分始终为 0。

我正在 x86 上为安卓使用内联 asm。很抱歉语法不正确或可能发生的其他错误,请理解我的意思(很难从我的代码中剥离出来(。

# Data
uint16_t dataSrc[] = {0x7000, 0x4401, 0x3801, 0xf002, 0x4800, 0xb802, 0x1800, 
0x3c00, 0xd800.....
uint16_t* src = dataSrc;
uint8_t * dst = new uint8_t[16] = {0};
uint8_t * map = new uint8_t[16] = { 9,8, 11,10, 13,12, 15,14, 1,0,3,2,5,4,7,6,};
# I need to convert 0x7000 to 0x0077 by shifting each 16 bit by its byte vectorized.
asm volatile (
"movdqu     (%0),%%xmm1n"
"pshufb     %2,%%xmm1n"
"movdqu     %%xmm1,(%1)n"
:   "+r" (src),
"+r" (dst),
"+r" (map)
:
:   "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4"
);

如果我遍历 dataSrc 变量,我的前 8 个字节的输出是:

0: 0
1: 0
2: 0
3: 0
4: 72
5: 696
6: 24
7: 60

只有最后 4 个被交换,即使它的顺序错误。为什么前 4 个都是零?无论我如何更改地图,第一个有时是 0,接下来的 3 总是零,为什么?我做错了什么吗?

编辑

我想出了为什么它不起作用,地图没有正确传递到内联 asm,我必须为它释放一个输入变量。

有关 intrisics 与手写 asm 的其他问题。下面的代码是将 16 字节的视频帧数据YUV42010BE转换为 YUVP420(8 位(,问题出在随机播放上,如果我使用小端序,那么我就不会有该部分。

static const char map[16] = { 9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6 };
int dstStrideOffset = (dstStride - srcStride / 2);
asm volatile (
"push       %%ebpn"
// All 0s for packing
"xorps      %%xmm0, %%xmm0n"
"movdqu     (%5),%%xmm4n"
"yloop:n"
// Set the counter for the stride
"mov %2,    %%ebpn"
"xloop:n"
// Load source data
"movdqu     (%0),%%xmm1n"
"movdqu     16(%0),%%xmm2n"
"add        $32,%0n"
// The first 4 16-bytes are 0,0,0,0, this is the issue.
"pshufb      %%xmm4, %%xmm1n"
"pshufb      %%xmm4, %%xmm2n"
// Shift each 16 bit to the right to convert
"psrlw      $0x2,%%xmm1n"
"psrlw      $0x2,%%xmm2n"
// Merge both 16bit vectors into 1 8bit vector
"packuswb   %%xmm0, %%xmm1n"
"packuswb   %%xmm0, %%xmm2n"
"unpcklpd   %%xmm2, %%xmm1n"
// Write the data
"movdqu     %%xmm1,(%1)n"
"add        $16, %1n"
// End loop, x = srcStride; x >= 0 ; x -= 32
"sub        $32, %%ebpn"
"jg         xloopn"
// End loop, y = height; y >= 0; --y
"add %4,    %1n"
"sub $1,    %3n"
"jg         yloopn"
"pop        %%ebpn"
:   "+r" (src),
"+r" (dst),
"+r" (srcStride),
"+r" (height),
"+r"(dstStrideOffset)
:   "x"(map)
:   "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4"
);

我还没有使用小端来实现内部函数的洗牌

const int dstStrideOffset = (dstStride - srcStride / 2);
__m128i mdata, mdata2;
const __m128i zeros = _mm_setzero_si128();
for (int y = height; y > 0; --y) {
for (int x = srcStride; x > 0; x -= 32) {
mdata = _mm_loadu_si128((const __m128i *)src);
mdata2 = _mm_loadu_si128((const __m128i *)(src + 8));
mdata = _mm_packus_epi16(_mm_srli_epi16(mdata, 2), zeros);
mdata2 = _mm_packus_epi16(_mm_srli_epi16(mdata2, 2), zeros);
_mm_storeu_si128( (__m128i *)dst, static_cast<__m128i>(_mm_unpacklo_pd(mdata, mdata2)));
src += 16;
dst += 16;
}
dst += dstStrideOffset;
}

可能写得不正确,但在 Android 模拟器 (API 27(、x86(SSSE3 是最高的,i686(上使用默认编译器设置和添加的优化进行基准测试,例如(尽管性能没有差异(-Ofast -O3 -funroll-loops -mssse3 -mfpmath=sse平均而言:

固有值:1.9-2.1 ms 手写:0.7-1ms

有没有办法加快速度?也许我写错了内在,是否有可能更接近用内联写的速度?

您的代码不起作用,因为您将map的地址传递给pshufb。 我不确定 gcc 为此生成了什么代码,我根本无法想象这能编译。

对于这种事情使用内联程序集通常不是一个好主意。 请改用内部函数:

#include <immintrin.h>
void byte_swap(char dst[16], const char src[16])
{
__m128i msrc, map, mdst;
msrc = _mm_loadu_si128((const _m128i *)src);
map = _mm_setr_epi8(9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6);
mdst = _mm_shuffle_epi8(msrc, map);
_mm_storeu_si128((_m128i *)dst, mdst);
}

除了更易于维护之外,这还可以更好地优化,因为取消链接内联程序集,编译器可以内省内部函数并就发出哪些指令做出明智的决定。 例如,在 AVX 目标上,它可能会发出 VEX 编码的vpshufb而不是pshufb以避免由于 AVX/SSE 转换而导致停顿。

如果由于任何原因无法使用内部函数,请使用如下所示的内联程序集:

void byte_swap(char dst[16], const char src[16])
{
typedef long long __m128i_u __attribute__ ((__vector_size__ (16), __may_alias__, __aligned__ (1)));
static const char map[16] = { 9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6 };
__m128i_u data = *(const __m128i_u *)src;
asm ("pshufb %1, %0" : "+x"(data) : "xm"(* (__m128i_u *)map));
*(__m128i_u *)dst = data;
}

最新更新