C++ 内联程序集中的子阵列。C++; void new2d(int*aInit,int*aResult) { int循环[34]={0}; 对于(int i=0;i

C++ 内联程序集中的子阵列。C++; void new2d(int*aInit,int*aResult) { int循环[34]={0}; 对于(int i=0;i,c++,assembly,x86-64,simd,inline-assembly,C++,Assembly,X86 64,Simd,Inline Assembly,尝试将一个初始数组的三个子数组相加,找到它们的平均值,并将它们保存在结果数组中。不过,在启动.exe文件后,它会显示分段错误。 我怎样才能修好它?使用GNU 2.9.3,Ubuntu您使用的是vmovdqa指令,它要求在数组的未对齐元素上使用对齐的内存操作数。对于加载和存储,请改用vmovdqu。或者更好的方法是,在实际计算指令中使用内存操作数(但这仅在AVX中有效;在传统SSE中,大多数指令的内存操作数必须对齐) 汇编程序块还有其他低效和问题。例如,如注释中所述,您缺少了clobbers,它指

尝试将一个初始数组的三个子数组相加,找到它们的平均值,并将它们保存在结果数组中。不过,在启动.exe文件后,它会显示分段错误。
我怎样才能修好它?使用GNU 2.9.3,Ubuntu

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

汇编程序块还有其他低效和问题。例如,如注释中所述,您缺少了clobbers,它指示可能被asm块修改的CPU和内存状态。在您的例子中,您缺少“内存”、“xmm0”和“xmm1”缓冲区。如果没有这些,编译器将假定asm块不会影响内存内容(特别是
aResult
数组)或
xmm
寄存器(例如,在与asm块冲突的情况下使用这些寄存器)

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

此外,对输入和输出数组的引用传递效率低下。与其将指针传递给数组的特定元素,不如将内存引用传递给这些元素,这样编译器就可以使用比普通指针更复杂的内存引用参数。这样就不需要在asm块之前在单独的指令中计算指针。此外,在
xmm
寄存器中传递
常量比在内存中传递更有效。理想情况下,您可能希望将
vbroadcasts
移出循环,但这只有在支持内部函数的情况下才可能实现。(或在一条asm语句中写入循环。)

更正和改进的asm语句如下所示:

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])
                );
        }
}
(由于内存输出是显式操作数,它实际上不必再是易变的。)


但更好的解决方案是使用内部函数来实现这个asm块。这不仅会使asm块更安全,而且会使它更高效,因为它将支持额外的编译器优化。当然,这只有在编译器支持intrinsic时才可能实现。

请注意,内联程序集无效,因为它没有声明适当的clobber。使用内部函数可以更好地完成这类任务。即使您安全地使用内联asm,您也会因为错过了许多优化而自食其果,例如,将
3.0
的负载从循环中提升出来,然后乘以
1.0/3
,而不是使用慢速除法。还将内存源操作数用于
vcvtdq2ps
。如果您的整数不会溢出,则执行整数加法,而不是转换为FP和back。此外,
j
的加载
循环[i+1+0..3]
可以通过类似于
vpaligner
的混洗从
cyclic[i]
向量和下一次迭代的
cyclic[i]
向量生成。未对齐的负载吞吐量通常非常好,因此可能不值得。如果您使用了内部函数,编译器可以为您执行其中一些操作,例如,将
vbroadcastss
\u mm\u set1\u ps(3.0f)
)从循环中提升出来;它还缺少一个
“内存”
缓冲区和它使用的XMM寄存器上的缓冲区。有缺陷的GNUC内联asm编译并似乎可以工作是非常危险的;一颗滴答作响的定时炸弹,等待着破解周围的密码。编写错误代码也很容易(也就是说,很难做到正确),所以我认为重要的是不要让内联asm代码中的错误在堆栈溢出答案中被忽略;这对任何人来说都是危险的,因为他们可能会复制/粘贴一些代码作为示例,而不阅读这么多命令。@PeterCordes我已经扩展了我的答案。
“vbroadcastss%4,%%xmm1;”
内部循环仍然很糟糕,但至少它不是一个正确性错误。如果不使用Intel Intrinsic,您可以像
xmmintrin.h
一样使用GNU C本机向量语法<代码>类型定义浮点v4f属性((向量大小(16)))__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" );