【发布时间】:2018-08-28 09:10:50
【问题描述】:
我一直在做 arm asm 并尝试使用 x86 asm ssse3 优化简单循环。我找不到将大端转换为小端的方法。
ARM NEON 有一个向量指令可以做到这一点,但 SSSE3 没有。我尝试使用 2 班次和一次或,但如果我们向左移动 8 位(数据饱和),则需要每个槽位 32 位而不是 16 位。
我查看了 PSHUFB,但是当我使用它时,16 位字的前半部分始终为 0。
我在 x86 for android 上使用内联 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),%%xmm1\n"
"pshufb %2,%%xmm1\n"
"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 总是为零,为什么?我是不是做错了什么?
编辑
我知道为什么它不起作用,映射没有正确传递到内联汇编中,我必须为它释放一个输入变量。
关于 intrisics 与手写 asm 的其他问题。下面的代码是将16字节视频帧数据YUV42010BE转换为YUVP420(8位),问题在于shuffle,如果我使用little endian,那我就没有那个部分了。
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 %%ebp\n"
// All 0s for packing
"xorps %%xmm0, %%xmm0\n"
"movdqu (%5),%%xmm4\n"
"yloop:\n"
// Set the counter for the stride
"mov %2, %%ebp\n"
"xloop:\n"
// Load source data
"movdqu (%0),%%xmm1\n"
"movdqu 16(%0),%%xmm2\n"
"add $32,%0\n"
// The first 4 16-bytes are 0,0,0,0, this is the issue.
"pshufb %%xmm4, %%xmm1\n"
"pshufb %%xmm4, %%xmm2\n"
// Shift each 16 bit to the right to convert
"psrlw $0x2,%%xmm1\n"
"psrlw $0x2,%%xmm2\n"
// Merge both 16bit vectors into 1 8bit vector
"packuswb %%xmm0, %%xmm1\n"
"packuswb %%xmm0, %%xmm2\n"
"unpcklpd %%xmm2, %%xmm1\n"
// Write the data
"movdqu %%xmm1,(%1)\n"
"add $16, %1\n"
// End loop, x = srcStride; x >= 0 ; x -= 32
"sub $32, %%ebp\n"
"jg xloop\n"
// End loop, y = height; y >= 0; --y
"add %4, %1\n"
"sub $1, %3\n"
"jg yloop\n"
"pop %%ebp\n"
: "+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 毫秒 手写:0.7-1ms
有没有办法加快速度?也许我写错了内在函数,是否有可能获得更接近用内在函数编写的速度?
【问题讨论】:
-
PSHUFB 在 SSSE3 中,而不是在 SSE3 中。它们是不同的扩展。如果您确实需要支持不带 SSSE3 的 CPU,则需要使用
psrlw/psllw/por来解决这些 CPU 问题。 (说真的,你应该把你的 x86 asm 翻译成内在函数。如果你的实际内联 asm 看起来像这样带有这样的约束,编译器至少会做得很好。特别是如果你真的使用new到 动态地分配16字节数组!) -
也许我应该上传我的整个代码集,我上面的代码是从更大的 asm 代码中提取的,我只是为了说明这个问题,这不是整个调用,否则我会使用内部函数。像我在下面的评论中所说的内在函数慢了 3 倍(可能写错了)。晚上回家后我会上传代码。 3-4ms vs 1ms 手写(这不包括任何 pshufb)。到目前为止,我一直在使用 psrlw / psllw / por。
-
哦,您的意思是 x86 内部函数的代码要慢 3 倍?我假设您正在避免内在函数,因为在 ARM 上使用它们的经验很糟糕,编译器做得很糟糕,而在 x86 上大多非常好。在 x86 上,clang 尤其经常在 asm 中使用比使用内在函数指定的更好的 shuffles / blends 做得很好;就像
+操作符不必编译成add指令一样,内在函数是 LLVM 的 shuffle 优化器的输入。无论如何,也许你没有让编译器优化,也许是通过使用别名分析失败的循环计数器来重新加载,IDK? -
无论如何,查看编译器为您的内在函数生成的 asm,看看编译器缺少什么。然后查看您的源代码,看看为什么它认为它必须在循环或其他任何内容中进行符号扩展或重新加载,并使用局部变量或
size_t计数器或指针增量或任何有助于编译器的东西。您最好的结果是可移植的面向未来的 C++,其内在函数可以使用当前编译器编译成很好的 asm。 不是手写的 asm 可能不是很好,但未来的 CPU 可能会很糟糕。 -
@user654628 您的问题在此期间得到解决了吗?如果是,如果您可以将答案标记为已接受,那就太好了。如果没有,请告诉我您遇到的问题,以便我帮助您解决其余问题。
标签: c++ x86 vectorization sse inline-assembly