1

我一直在使用演示文稿中的示例(幻灯片 41)。

就我而言,它执行 alpha 混合。

MOVQ mm0, alpha//4 16-b zero-padding α
MOVD mm1, A //move 4 pixels of image A 
MOVD mm2, B //move 4 pixels of image B
PXOR mm3 mm3 //clear mm3 to all zeroes 
//unpack 4 pixels to 4 words
PUNPCKLBW mm1, mm3 // Because B -A could be
PUNPCKLBW mm2, mm3 // negative, need 16 bits
PSUBW mm1, mm2 //(B-A) 
PMULHW mm1, mm0 //(B-A)*fade/256 
PADDW mm1, mm2 //(B-A)*fade + B 
//pack four words back to four bytes
PACKUSWB mm1, mm3

我想用汇编程序用c重写它。

现在,我有这样的事情:

void fade_mmx(SDL_Surface* im1,SDL_Surface* im2,Uint8 alpha, SDL_Surface* imOut)
{
    int pixelsCount = imOut->w * im1->h;
    
    Uint32 *A = (Uint32*) im1->pixels;
    Uint32 *B = (Uint32*) im2->pixels;
    Uint32 *out = (Uint32*) imOut->pixels;
    Uint32 *end = out + pixelsCount;

    __asm__ __volatile__ (
            "\n\t movd  (%0), %%mm0"
            "\n\t movd  (%1), %%mm1"
            "\n\t movd  (%2), %%mm2"
            "\n\t pxor       %%mm3, %%mm3"
            "\n\t punpcklbw  %%mm3, %%mm1"
            "\n\t punpcklbw  %%mm3, %%mm2"
            "\n\t psubw      %%mm2, %%mm1"
            "\n\t pmulhw     %%mm0, %%mm1"
            "\n\t paddw      %%mm2, %%mm1"
            "\n\t packuswb   %%mm3, %%mm1"
    : : "r" (alpha), "r" (A), "r" (B), "r" (out), "r" (end)
    );
    __asm__("emms" : : );
}

编译时我收到此消息:Error: (%dl) is not a valid base/index expression关于汇编程序中的第一行。我怀疑这是因为alphais Uint8,我尝试投射它,但后来出现分段错误。在这个例子中,他们谈论4 16-b zero-padding α的是我不太清楚的。

4

2 回答 2

2

您的问题是您试图将您的alpha值用作地址而不是值。该movd (%0), %%mm0指令说%0用作内存中的位置。所以你说加载指向的值alpha而不是它的值。Usingmovd %0, %%mm0可以解决这个问题,但是你会遇到一个问题,即你的alpha值只有 8 位类型,并且它需要是 32 位类型才能与 MOVD 指令一起使用。您可以解决该问题,并且该alpha值需要乘以 256 并广播到目标寄存器的所有 4 个 16 位字,以便您的算法通过乘以0x0100010001000100ULL并使用 MOVQ 指令来工作。

但是,您根本不需要 MOVD/MOVQ 指令。y您可以通过使用如下代码指定约束,让编译器将值加载到 MMX 寄存器本身:

typedef unsigned pixel;

static inline pixel
fade_pixel_mmx_asm(pixel p1, pixel p2, unsigned fade) {
    asm("punpcklbw %[zeros], %[p1]\n\t"
        "punpcklbw %[zeros], %[p2]\n\t"
        "psubw     %[p2], %[p1]\n\t"
        "pmulhw    %[fade], %[p1]\n\t"
        "paddw     %[p2], %[p1]\n\t"
        "packuswb  %[zeros], %[p1]"
        : [p1] "+&y" (p1), [p2] "+&y" (p2)
        : [fade] "y" (fade * 0x0100010001000100ULL), [zeros] "y" (0));
    return p1;
}

您会注意到这里不需要clobber 列表,因为没有使用不是由编译器分配的寄存器,也没有编译器需要知道的其他副作用。我省略了必要的 EMMS 指令,因为您不想在每个像素上执行。您需要asm("emms");在混合两个曲面的循环之后插入一个语句。

更好的是,您根本不需要使用内联汇编。您可以改用内在函数,而不必担心使用内联汇编的所有缺陷:

#include <mmintrin.h>

static inline pixel
fade_pixel_mmx_intrin(pixel p1, pixel p2, unsigned fade) {
    __m64 zeros = (__m64) 0ULL;
    __m#64 mfade = (__m64) (fade * 0x0100010001000100ULL);
    __m64 mp1 = _m_punpcklbw((__m64) (unsigned long long) p1, zeros);
    __m64 mp2 = _m_punpcklbw((__m64) (unsigned long long) p2, zeros);

    __m64 ret;
    ret = _m_psubw(mp1, mp2);
    ret = _m_pmulhw(ret, mfade);
    ret = _m_paddw(ret, mp2);
    ret = _m_packuswb(ret, zeros);

    return (unsigned long long) ret;
}
    

与前面的示例类似,您需要_m_empty()在循环后调用以生成必要的 EMMS 指令。

您还应该认真考虑只用普通 C 编写例程。如今,自动向量化器非常好,而且编译器使用现代 SIMD 指令生成的代码可能比您尝试使用古老的 MMX 指令生成的代码更好。例如,这段代码:

static inline unsigned
fade_component(unsigned c1, unsigned c2, unsigned fade) {
    return c2  + (((int) c1 - (int) c2) * fade) / 256;
}

void
fade_blend(pixel *dest, pixel *src1, pixel *src2, unsigned char fade,
           unsigned len) {
    unsigned char *d = (unsigned char *) dest;
    unsigned char *s1 = (unsigned char *) src1;
    unsigned char *s2 = (unsigned char *) src2;
    unsigned i;
    for (i = 0; i < len * 4; i++) {
        d[i] = fade_component(s1[i], s2[i], fade);
    }
}

使用 GCC 10.2 和-O3上述代码会生成使用 128 位 XMM 寄存器并在其内部循环中一次混合 4 个像素的汇编代码:

    movdqu  xmm5, XMMWORD PTR [rdx+rax]
    movdqu  xmm1, XMMWORD PTR [rsi+rax]
    movdqa  xmm6, xmm5
    movdqa  xmm0, xmm1
    punpckhbw       xmm1, xmm3
    punpcklbw       xmm6, xmm3
    punpcklbw       xmm0, xmm3
    psubw   xmm0, xmm6
    movdqa  xmm6, xmm5
    punpckhbw       xmm6, xmm3
    pmullw  xmm0, xmm2
    psubw   xmm1, xmm6
    pmullw  xmm1, xmm2
    psrlw   xmm0, 8
    pand    xmm0, xmm4
    psrlw   xmm1, 8
    pand    xmm1, xmm4
    packuswb        xmm0, xmm1
    paddb   xmm0, xmm5
    movups  XMMWORD PTR [rdi+rax], xmm0

最后,即使是 C 代码的未矢量化版本也可能接近最优,因为代码足够简单,以至于无论混合的实现方式如何,您都可能会受到内存限制。

于 2020-12-29T01:05:58.993 回答
2

在复制到 MM reg 之前,您可以使用标量乘法广播alpha到 64 位。0x0001000100010001ULL另一种选择是将 8 位整数零扩展为 32 位movd,然后pshufw复制它。

您的 asm 也存在各种安全问题。

#include <SDL/SDL.h>
#include <stdint.h>

void fade_mmx(SDL_Surface* im1,SDL_Surface* im2,Uint8 alpha, SDL_Surface* imOut)
{
    int pixelsCount = imOut->w * im1->h;

    Uint32 *A = (Uint32*) im1->pixels;
    Uint32 *B = (Uint32*) im2->pixels;
    Uint32 *out = (Uint32*) imOut->pixels;
    Uint32 *end = out + pixelsCount;

    Uint64 alphas = (Uint64)alpha * 0x0001000100010001ULL;

    __asm__ __volatile__ (
            "\n\t movd  %0, %%mm0"
            "\n\t movd  %1, %%mm1"
            "\n\t movd  %2, %%mm2"
            "\n\t pxor       %%mm3, %%mm3"
            "\n\t punpcklbw  %%mm3, %%mm1"
            "\n\t punpcklbw  %%mm3, %%mm2"
            "\n\t psubw      %%mm2, %%mm1"
            "\n\t pmulhw     %%mm0, %%mm1"
            "\n\t paddw      %%mm2, %%mm1"
            "\n\t packuswb   %%mm3, %%mm1"
    : // you're probably going to want an "=m"(*something) memory output here
    : "r" (alphas), "m" (*A), "m" (*B), "r" (out), "r" (end)
    : "mm0", "mm1", "mm2", "mm3");
    __asm__("emms" : : );
}

volatile如果编译器知道所有输入和输出,而不是依赖于"memory"clobber ,则不需要 asm 语句。(就像这里一样,没有输出,只读取作为输入操作数的寄存器和内存。)

对于 32 位代码,替换"r"(alphas)"m"(alphas). 或用于"rm"(alphas)让编译器选择。(但是对于 32 位,使用 pshufw 肯定更好,而不是让编译器将 64 位乘法结果存储为 2 个 32 位的一半,然后在使用 movq 重新加载它时遭受存储转发停顿。内在会留下决定到编译器_mm_set1_epi16(alpha),尽管无论如何你只能在循环外执行一次)。

请注意,我还添加了必要的 clobber 列表并将包含您取消引用的指针的寄存器操作数替换为引用您取消引用的内存的内存操作数,从而允许 gcc 推断您访问的内存

请注意,如果您不解决这些问题,gcc 将会不高兴并且您的代码行为将是未定义的,可能会以神秘且难以调试的方式失败。除非您完全了解自己在做什么,否则不要使用内联汇编。考虑使用内在函数作为更安全且可能更有效的替代方案。(https://gcc.gnu.org/wiki/DontUseInlineAsm)。

带有向量的 SSE2__m128i可以轻松地一次处理 4 个像素,而不是 2 或 1pack因填充零而浪费一半的吞吐量。(punpckhbw用于补充punpcklbw为此设置)。MMX 已经过时,以至于现代 CPU 对某些指令的 MMX 版本的吞吐量低于等效的 128 位 SSE2 XMM 指令的吞吐量。

于 2020-12-27T21:59:47.407 回答