内联汇编中的子数组。 C ++

时间:2020-04-30 07:49:49

标签: c++ assembly x86-64 simd inline-assembly

void new2d(int* aInit, int* aResult)
{
        int cyclic[34] = {0};
        for (int i = 0; i < 32; i++)
        {
                cyclic[i] = aInit[i];
        }
        cyclic[32] = aInit[0];
        cyclic[33] = aInit[1];
        float three = 3.0;
        for (int i = 0; i < 32; i += 4)
        {
                int j = i + 1;
                int k = j + 1;
                __asm__ __volatile__
                (
                "vmovdqa (%0), %%xmm0;"
                "vmovdqa (%1), %%xmm1;"
                "vcvtdq2ps %%xmm0, %%xmm0;"
                "vcvtdq2ps %%xmm1, %%xmm1;"
                "addps %%xmm0, %%xmm1;"
                "vmovdqa (%2), %%xmm1;"
                "vcvtdq2ps %%xmm1, %%xmm1;"
                "addps %%xmm0, %%xmm1;"
                "vbroadcastss (%3), %%xmm1;"
                "divps %%xmm0, %%xmm1;"
                "vcvtps2dq %%xmm0, %%xmm0;"
                "vmovdqa %%xmm0, (%4);"
                :
                : "a"(&(cyclic[i])), "b"(&(cyclic[j])), "c"(&(cyclic[k])), "d"(&three), "S"(&aResult[i])
                );
        }
}

试图将一个初始数组的三个子数组相加,找到它们的平均值,然后将它们保存在结果数组中。但是,启动.exe文件后,它显示了Segmentation Fault。 我该如何解决?使用GNU 2.9.3,Ubuntu

1 个答案:

答案 0 :(得分:1)

您正在使用vmovdqa指令,该指令在数组的未对齐元素上需要对齐的内存操作数。对于加载和存储,请改用vmovdqu。或者更好的方法是,在实际的计算指令中使用内存操作数(尽管这仅在AVX中有效;在大多数SSE的旧版SSE内存操作数中必须对齐)。

装配块还有其他效率低下和问题的地方。例如,如评论中所述,您缺少了Clobbers,这些Clobbys指示可能由asm块修改的CPU和内存状态。就您而言,您缺少“内存”,“ xmm0”和“ xmm1”修饰符。没有这些,编译器将假定asm块不影响内存内容(尤其是aResult数组)或xmm寄存器(例如,在冲突时出于自身目的使用这些寄存器)和您的asm块)。

此外,由于在某些情况下您正在覆盖或不使用先前指令的结果,因此您似乎弄乱了addpsdivps指令中的输入和输出寄存器。在gcc使用的AT&T x86 asm语法中,最后一个操作数是输出操作数。通常,在使用任何AVX指令时都应使用每个指令的AVX版本,尽管如果YMM寄存器的上半部分已经清除(例如vzeroupper),则将128位AVX指令与传统SSE混合不会导致SSE / AVX转换停顿。使用vaddps / vdivps是可选的,但建议使用。

此外,您还无法有效地将引用传递到输入和输出数组。与将指针传递给数组的特定元素相比,将指针传递给数组的特定元素更为有效,这使编译器可以使用比普通指针更复杂的内存引用参数。这样就无需在asm块之前在单独的指令中计算指针。另外,在tree寄存器中而不是在存储器中传递xmm常量会更有效。理想情况下,您希望将vbroadcastss移出循环,但这只有在支持内在函数的情况下才有可能。 (或在一个asm语句中编写循环。)

经过纠正和改进的asm语句如下所示:

__asm__ __volatile__
(
    "vcvtdq2ps %1, %%xmm0;"
    "vcvtdq2ps %2, %%xmm1;"
    "vaddps %%xmm1, %%xmm0;"
    "vcvtdq2ps %3, %%xmm1;"
    "vaddps %%xmm1, %%xmm0;"
    "vbroadcastss %4, %%xmm1;"
    "vdivps %%xmm0, %%xmm1;"
    "vcvtps2dq %%xmm1, %0;"
    : "=m"(aResult[i])
    : "m"(cyclic[i]), "m"(cyclic[j]), "m"(cyclic[k]), "x"(three)
    : "memory", "xmm0", "xmm1"
);

(由于内存输出是显式操作数,因此实际上不必再是volatile。)

但是更好的解决方案是使用内部函数来实现此asm块。这不仅使asm块更安全,而且使它更有效,因为它将启用其他编译器优化。当然,只有在编译器支持内在函数的情况下才有可能。