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

C++ 尝试使用x86 asm SSSE3将大端转换为小端,c++,x86,vectorization,sse,inline-assembly,C++,X86,Vectorization,Sse,Inline Assembly,我已经做了一段时间的arm asm,并尝试使用x86 asm ssse3优化简单循环。我找不到一种方法把大端词转换成小端词 ARM NEON有一个单独的向量指令来实现这一点,但SSSE3没有。我尝试使用2个移位和or,但如果向左移位8(数据饱和),则每个插槽需要32位,而不是16位 我查看了PSHUFB,但当我使用它时,16位字的前半部分总是0 我在x86上为android使用内联asm。对于可能出现的错误语法或其他错误,请理解我的意思(很难从我的代码中删除) 如果我循环使用dataSrc变量,

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

ARM NEON有一个单独的向量指令来实现这一点,但SSSE3没有。我尝试使用2个移位和or,但如果向左移位8(数据饱和),则每个插槽需要32位,而不是16位

我查看了PSHUFB,但当我使用它时,16位字的前半部分总是0

我在x86上为android使用内联asm。对于可能出现的错误语法或其他错误,请理解我的意思(很难从我的代码中删除)

如果我循环使用dataSrc变量,则前8个字节的输出为:

0: 0
1: 0
2: 0
3: 0
4: 72
5: 696
6: 24
7: 60
即使顺序错误,也只交换最后4个。为什么前4个都是零?不管我如何改变地图,第一个有时是0,下一个3总是0,为什么?我做错什么了吗

编辑

我弄明白了为什么它不起作用,映射没有正确地传递到内联asm中,我必须为它释放一个输入变量

关于Intrisic与手写asm的其他问题。下面的代码是将16字节的视频帧数据YUV42010BE转换为YUVP420(8位),问题在于洗牌,如果我使用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"
);
我还没有来得及使用little endian实现内部函数的洗牌

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;
}
const int dststripdoffset=(dstStride-srcStride/2);
__m128i mdata,mdata2;
常数m128i零=_mm_setzero_si128();
对于(int y=高度;y>0;--y){
对于(int x=srcStride;x>0;x-=32){
mdata=_mm_loadu_si128((常数m128i*)src);
mdata2=_mm_loadu_si128((常数m128i*)(src+8));
mdata=_mm_packus_epi16(_mm_srli_epi16(mdata,2),零);
mdata2=_mm_packus_epi16(_mm_srli_epi16(mdata2,2),零);
_mm_storeu_si128((__m128i*)dst,静态铸造(_mm_Unpaclo_pd(mdata,mdata2));
src+=16;
dst+=16;
}
dst+=dst偏移量;
}
可能写得不正确,但在Android emulator(API 27)、x86(SSSE3是最高的,i686)上进行基准测试,并使用默认编译器设置和添加优化(尽管在性能上没有差异)-Ofast-O3-funroll循环-mssse3-mfpmath=sse平均值:

内在:1.9-2.1毫秒 手写:0.7-1ms


有没有办法加快速度?也许我写的intrinics是错的,有没有可能让intrinics的手写速度更接近呢?

你的代码不起作用,因为你把
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);
}

我在讨论内部函数,但是arm比编译器运行循环(我进行了基准测试)慢,相比于完整的neon汇编,大约慢20倍。测试相同的算法,但使用内部函数和循环要慢3-4倍。我的简单循环的每次迭代对于asm是1ms,对于Intrinsic是3-4ms,可能是带有ndk的llvm编译器。我以前在桌面上使用过intrinics,但我使用的是微软的visualstudio编译器。pshufb命令在您的代码中起作用,我一定是在某个地方出错了,是的,我写错了,意思是内联的“m”(map),而不是“+r”。@user654628 intrinsic如果在没有优化的情况下编译,可能会很慢,所以总是在启用优化的情况下编译。给编译器一个内联相关函数的机会也是很有帮助的。@user654628也许你可以在一个单独的问题中用内部函数发布你的ARM代码,我们可以试着找出它慢的原因。奇怪的是,Android总是使用优化进行编译。。。我的arm代码不再以内部语言编写,大多数都是用手写的汇编语言编写的,我以后可能会重写它。@user654628我明白了。如果您还有其他问题,请告诉我。如果您的所有问题都已回答,请将答案标记为已接受,以便其他用户知道您的问题已解决。PSHUFB位于SSSE3中,而不是SSE3中。它们是不同的扩展。如果您确实需要支持没有SSSE3的CPU,那么您需要为这些CPU提供一种变通方法,使用
psrlw
/
psllw
/
por
。(说真的,您应该将x86 asm转换为内部函数。如果您的实际内联asm看起来像这样,并且有这样的约束,那么编译器至少会做得一样好。特别是如果您确实在使用
new
动态分配16字节数组!)也许我应该上传我的整个代码集,我上面的代码是从更大的asm代码中提取出来的,我只是为了说明这个问题,这不是全部调用,否则我会使用intrinics。正如我在下面的评论中所说,Intrinics的速度慢了3倍(可能是写错了)。我今晚回家后会上传代码。3-4ms vs 1 ms用于手写(不包括任何pshufb)。到目前为止,我一直在使用psrlw/psllw/por。哦,你是说x86内部函数的代码要慢3倍?我认为您之所以避免使用intrinsic,是因为在ARM上使用它们的经验不好,在ARM上编译器的工作非常糟糕,而在x86上则大多非常好。在x86上,clang在asm中使用的洗牌/混合比使用Intrinsic指定的洗牌/混合效果更好;就像
+
操作符不必编译成
add
指令一样,内部函数是LLVM的shuffle优化器的输入。不管怎么说,也许你没有让编译器优化,也许是因为在别名分析失败的情况下使用循环计数器,所以你会重新加载,IDK?不管怎么说,看看编译器为你的内部函数生成asm,看看编译器缺少什么。然后看看你的消息来源,看看为什么它认为它必须签署扩展o
#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);
}
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;
}