内联汇编中的子数组。 C++
Subarrays in inline-assembly. C++
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
您正在对数组的未对齐元素使用 vmovdqa
指令,该指令需要对齐的内存操作数。使用 vmovdqu
代替,用于加载和存储。或者更好的是,在实际计算指令中使用内存操作数(尽管这仅在 AVX 中有效;在旧版 SSE 中,大多数指令的内存操作数必须对齐)。
汇编程序块还有其他低效和问题。例如,如评论中所述,您遗漏了 clobber,它表示 CPU 的片段和 asm 块可能修改的内存状态。在您的情况下,您缺少 "memory"、"xmm0" 和 "xmm1" 破坏者。没有这些,编译器将假设 asm 块不影响内存内容(特别是 aResult
数组)或 xmm
寄存器(并且,例如,将这些寄存器用于冲突中的目的用你的 asm 块)。
此外,您似乎在 addps
和 divps
指令中弄乱了输入和输出寄存器,因为您在某些情况下覆盖或不使用先前指令的结果。在 gcc 使用的 AT&T x86 asm 语法中,最后一个操作数是输出操作数。在使用任何 AVX 指令时,您通常应该使用每条指令的 AVX 版本,尽管如果 YMM 寄存器的上半部分已经干净(例如 vzeroupper ).使用 vaddps
/ vdivps
是可选的,但建议使用。
此外,您正在低效地传递对输入和输出数组的引用。与其将指针传递给数组的特定元素,不如将内存引用传递给这些元素更有效,这允许编译器使用比普通指针更复杂的内存引用参数。这消除了在 asm 块之前的单独指令中计算指针的需要。此外,在 xmm
寄存器而不是内存中传递 tree
常量效率更高。理想情况下,您希望将 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 块更安全,还会使其更高效,因为它会启用额外的编译器优化。当然,这只有在您的编译器支持内部函数时才有可能。
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
您正在对数组的未对齐元素使用 vmovdqa
指令,该指令需要对齐的内存操作数。使用 vmovdqu
代替,用于加载和存储。或者更好的是,在实际计算指令中使用内存操作数(尽管这仅在 AVX 中有效;在旧版 SSE 中,大多数指令的内存操作数必须对齐)。
汇编程序块还有其他低效和问题。例如,如评论中所述,您遗漏了 clobber,它表示 CPU 的片段和 asm 块可能修改的内存状态。在您的情况下,您缺少 "memory"、"xmm0" 和 "xmm1" 破坏者。没有这些,编译器将假设 asm 块不影响内存内容(特别是 aResult
数组)或 xmm
寄存器(并且,例如,将这些寄存器用于冲突中的目的用你的 asm 块)。
此外,您似乎在 addps
和 divps
指令中弄乱了输入和输出寄存器,因为您在某些情况下覆盖或不使用先前指令的结果。在 gcc 使用的 AT&T x86 asm 语法中,最后一个操作数是输出操作数。在使用任何 AVX 指令时,您通常应该使用每条指令的 AVX 版本,尽管如果 YMM 寄存器的上半部分已经干净(例如 vzeroupper ).使用 vaddps
/ vdivps
是可选的,但建议使用。
此外,您正在低效地传递对输入和输出数组的引用。与其将指针传递给数组的特定元素,不如将内存引用传递给这些元素更有效,这允许编译器使用比普通指针更复杂的内存引用参数。这消除了在 asm 块之前的单独指令中计算指针的需要。此外,在 xmm
寄存器而不是内存中传递 tree
常量效率更高。理想情况下,您希望将 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 块更安全,还会使其更高效,因为它会启用额外的编译器优化。当然,这只有在您的编译器支持内部函数时才有可能。