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
答案 0 :(得分:1)
您正在使用vmovdqa
指令,该指令在数组的未对齐元素上需要对齐的内存操作数。对于加载和存储,请改用vmovdqu
。或者更好的方法是,在实际的计算指令中使用内存操作数(尽管这仅在AVX中有效;在大多数SSE的旧版SSE内存操作数中必须对齐)。
装配块还有其他效率低下和问题的地方。例如,如评论中所述,您缺少了Clobbers,这些Clobbys指示可能由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块之前在单独的指令中计算指针。另外,在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块更安全,而且使它更有效,因为它将启用其他编译器优化。当然,只有在编译器支持内在函数的情况下才有可能。