- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
当使用内联汇编循环遍历数组时,我应该使用寄存器修饰符“r”还是内存修饰符“m”?
让我们考虑一个添加两个浮点数组 x
的例子, 和 y
并将结果写入 z
.通常我会使用内在函数来做到这一点
for(int i=0; i<n/4; i++) {
__m128 x4 = _mm_load_ps(&x[4*i]);
__m128 y4 = _mm_load_ps(&y[4*i]);
__m128 s = _mm_add_ps(x4,y4);
_mm_store_ps(&z[4*i], s);
}
void add_asm1(float *x, float *y, float *z, unsigned n) {
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
"movaps (%1,%%rax,4), %%xmm0\n"
"addps (%2,%%rax,4), %%xmm0\n"
"movaps %%xmm0, (%0,%%rax,4)\n"
:
: "r" (z), "r" (y), "r" (x), "a" (i)
:
);
}
}
rax
.是否有一个原因?
void add_asm2(float *x, float *y, float *z, unsigned n) {
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
"movaps %1, %%xmm0\n"
"addps %2, %%xmm0\n"
"movaps %%xmm0, %0\n"
: "=m" (z[i])
: "m" (y[i]), "m" (x[i])
:
);
}
}
gcc -O3 -S asmtest.c
):
.L22
movaps (%rsi), %xmm0
addps (%rdi), %xmm0
movaps %xmm0, (%rdx)
addl $4, %eax
addq $16, %rdx
addq $16, %rsi
addq $16, %rdi
cmpl %eax, %ecx
ja .L22
#include <stdio.h>
#include <x86intrin.h>
#define N 64
void add_intrin(float *x, float *y, float *z, unsigned n) {
for(int i=0; i<n; i+=4) {
__m128 x4 = _mm_load_ps(&x[i]);
__m128 y4 = _mm_load_ps(&y[i]);
__m128 s = _mm_add_ps(x4,y4);
_mm_store_ps(&z[i], s);
}
}
void add_intrin2(float *x, float *y, float *z, unsigned n) {
for(int i=0; i<n/4; i++) {
__m128 x4 = _mm_load_ps(&x[4*i]);
__m128 y4 = _mm_load_ps(&y[4*i]);
__m128 s = _mm_add_ps(x4,y4);
_mm_store_ps(&z[4*i], s);
}
}
void add_asm1(float *x, float *y, float *z, unsigned n) {
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
"movaps (%1,%%rax,4), %%xmm0\n"
"addps (%2,%%rax,4), %%xmm0\n"
"movaps %%xmm0, (%0,%%rax,4)\n"
:
: "r" (z), "r" (y), "r" (x), "a" (i)
:
);
}
}
void add_asm2(float *x, float *y, float *z, unsigned n) {
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
"movaps %1, %%xmm0\n"
"addps %2, %%xmm0\n"
"movaps %%xmm0, %0\n"
: "=m" (z[i])
: "m" (y[i]), "m" (x[i])
:
);
}
}
int main(void) {
float x[N], y[N], z1[N], z2[N], z3[N];
for(int i=0; i<N; i++) x[i] = 1.0f, y[i] = 2.0f;
add_intrin2(x,y,z1,N);
add_asm1(x,y,z2,N);
add_asm2(x,y,z3,N);
for(int i=0; i<N; i++) printf("%.0f ", z1[i]); puts("");
for(int i=0; i<N; i++) printf("%.0f ", z2[i]); puts("");
for(int i=0; i<N; i++) printf("%.0f ", z3[i]); puts("");
}
最佳答案
尽可能避免内联汇编:https://gcc.gnu.org/wiki/DontUseInlineAsm .它阻止了许多优化。但是,如果您真的无法通过编译器来制作您想要的 asm,您可能应该在 asm 中编写整个循环,以便您可以手动展开和调整它,而不是做这样的事情。
您可以使用 r
索引的约束。使用 q
修饰符来获取 64 位寄存器的名称,因此您可以在寻址模式下使用它。当为 32 位目标编译时,q
修饰符选择 32 位寄存器的名称,因此相同的代码仍然有效。
如果你想选择使用哪种寻址模式,你需要自己做,使用带有 r
的指针操作数。约束。
GNU C 内联 asm 语法不假设您读取或写入指针操作数指向的内存。 (例如,也许您在指针值上使用了 inline-asm and
)。所以你需要用 "memory"
做一些事情clobber 或内存输入/输出操作数,让它知道你修改了什么内存。一个 "memory"
clobber 很容易,但强制除本地人之外的所有内容都被溢出/重新加载。见 Clobbers section in the docs有关使用虚拟输入操作数的示例。
具体来说,一个 "m" (*(const float (*)[]) fptr)
将告诉编译器整个数组对象是一个任意长度的输入 .即 asm 不能与任何使用 fptr
的商店重新排序作为地址的一部分(或使用它已知指向的数组)。也适用于 "=m"
或 "+m"
约束(显然没有 const
)。
使用 特定尺寸,如 "m" (*(const float (*)[4]) fptr)
让你告诉编译器你做什么/不读什么。 (或写)。然后它可以(如果另外允许的话)将存储沉入 asm
之后的元素。语句,并将其与您的内联 asm 未读取的任何存储的另一个存储(或执行死存储消除)结合起来。
(有关此问题的完整问答,请参阅 How can I indicate that the memory *pointed* to by an inline ASM argument may be used?。)
m
的另一个巨大好处约束是 -funroll-loops
可以工作通过生成具有恒定偏移量的地址。我们自己进行寻址可以防止编译器每 4 次迭代或其他事情进行一次增量,因为 i
的每个源级值需要出现在寄存器中。
这是我的版本,在评论中做了一些调整。这不是最佳的,例如编译器无法有效展开。
#include <immintrin.h>
void add_asm1_memclobber(float *x, float *y, float *z, unsigned n) {
__m128 vectmp; // let the compiler choose a scratch register
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
"movaps (%[y],%q[idx],4), %[vectmp]\n\t" // q modifier: 64bit version of a GP reg
"addps (%[x],%q[idx],4), %[vectmp]\n\t"
"movaps %[vectmp], (%[z],%q[idx],4)\n\t"
: [vectmp] "=x" (vectmp) // "=m" (z[i]) // gives worse code if the compiler prepares a reg we don't use
: [z] "r" (z), [y] "r" (y), [x] "r" (x),
[idx] "r" (i) // unrolling is impossible this way (without an insn for every increment by 4)
: "memory"
// you can avoid a "memory" clobber with dummy input/output operands
);
}
}
Godbolt compiler explorer这个和下面的几个版本的 asm 输出。
%xmm0
被破坏,否则当它被内联时你会过得很糟糕。我的版本使用一个临时变量作为从未使用过的仅输出操作数。这为编译器分配寄存器提供了完全的自由。
"m" (*(const __m128*)&x[i])
告诉编译器您的函数读取和写入哪个内存。如果你做了类似
x[4] = 1.0;
的事情,这对于确保正确的代码生成是必要的。在运行该循环之前。 (即使你没有写那么简单的东西,内联和常量传播也可以归结为那个。)还要确保编译器不会从
z[]
中读取。在循环运行之前。
[reg]
寻址模式而不是索引。它不知道内联汇编实际上从未使用约束创建的寻址模式引用那些内存操作数!
# gcc5.4 with dummy constraints like "=m" (*(__m128*)&z[i]) instead of "memory" clobber
.L11:
movaps (%rsi,%rax,4), %xmm0 # y, i, vectmp
addps (%rdi,%rax,4), %xmm0 # x, i, vectmp
movaps %xmm0, (%rdx,%rax,4) # vectmp, z, i
addl $4, %eax #, i
addq $16, %r10 #, ivtmp.19
addq $16, %r9 #, ivtmp.21
addq $16, %r8 #, ivtmp.22
cmpl %eax, %ecx # i, n
ja .L11 #,
r8、r9 和 r10 是内联 asm 块不使用的额外指针。
"m" (*(const char (*)[]) pStr)
.这将指针转换为指向数组的指针(未指定大小)。见
How can I indicate that the memory *pointed* to by an inline ASM argument may be used?
"memory"
破坏,但仍然不容易被编译器解开)。
void add_asm1_dummy_whole_array(const float *restrict x, const float *restrict y,
float *restrict z, unsigned n) {
__m128 vectmp; // let the compiler choose a scratch register
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
"movaps (%[y],%q[idx],4), %[vectmp]\n\t" // q modifier: 64bit version of a GP reg
"addps (%[x],%q[idx],4), %[vectmp]\n\t"
"movaps %[vectmp], (%[z],%q[idx],4)\n\t"
: [vectmp] "=x" (vectmp)
, "=m" (*(float (*)[]) z) // "=m" (z[i]) // gives worse code if the compiler prepares a reg we don't use
: [z] "r" (z), [y] "r" (y), [x] "r" (x),
[idx] "r" (i) // unrolling is impossible this way (without an insn for every increment by 4)
, "m" (*(const float (*)[]) x),
"m" (*(const float (*)[]) y) // pointer to unsized array = all memory from this pointer
);
}
}
这为我们提供了与
"memory"
相同的内部循环破坏者:
.L19: # with clobbers like "m" (*(const struct {float a; float x[];} *) y)
movaps (%rsi,%rax,4), %xmm0 # y, i, vectmp
addps (%rdi,%rax,4), %xmm0 # x, i, vectmp
movaps %xmm0, (%rdx,%rax,4) # vectmp, z, i
addl $4, %eax #, i
cmpl %eax, %ecx # i, n
ja .L19 #,
它告诉编译器每个 asm 块读取或写入整个数组,因此它可能会不必要地阻止它与其他代码交错(例如,在以低迭代计数完全展开之后)。它不会停止展开,但要求在寄存器中包含每个索引值确实降低了它的效率。这不可能以
16(%rsi,%rax,4)
结束。在同一循环中此块的第二个副本中的寻址模式,因为我们对编译器隐藏了寻址。
m
的版本约束,that gcc can unroll :
#include <immintrin.h>
void add_asm1(float *x, float *y, float *z, unsigned n) {
// x, y, z are assumed to be aligned
__m128 vectmp; // let the compiler choose a scratch register
for(int i=0; i<n; i+=4) {
__asm__ __volatile__ (
// "movaps %[yi], %[vectmp]\n\t" // get the compiler to do this load instead
"addps %[xi], %[vectmp]\n\t"
"movaps %[vectmp], %[zi]\n\t"
// __m128 is a may_alias type so these casts are safe.
: [vectmp] "=x" (vectmp) // let compiler pick a stratch reg
,[zi] "=m" (*(__m128*)&z[i]) // actual memory output for the movaps store
: [yi] "0" (*(__m128*)&y[i]) // or [yi] "xm" (*(__m128*)&y[i]), and uncomment the movaps load
,[xi] "xm" (*(__m128*)&x[i])
//, [idx] "r" (i) // unrolling with this would need an insn for every increment by 4
);
}
}
使用
[yi]
作为
+x
输入/输出操作数会更简单,但以这种方式编写它可以对内联 asm 中的加载取消注释进行较小的更改,而不是让编译器为我们将一个值放入寄存器。
关于c - 使用内联汇编循环遍历数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34244185/
我是 PHP 新手。我一直在脚本中使用 for 循环、while 循环、foreach 循环。我想知道 哪个性能更好? 选择循环的标准是什么? 当我们在另一个循环中循环时应该使用哪个? 我一直想知道要
我在高中的编程课上,我的作业是制作一个基本的小计和顶级计算器,但我在一家餐馆工作,所以制作一个只能让你在一种食物中读到。因此,我尝试让它能够接收多种食品并将它们添加到一个价格变量中。抱歉,如果某些代码
这是我正在学习的一本教科书。 var ingredients = ["eggs", "milk", "flour", "sugar", "baking soda", "baking powder",
我正在从字符串中提取数字并将其传递给函数。我想给它加 1,然后返回字符串,同时保留前导零。我可以使用 while 循环来完成此操作,但不能使用 for 循环。 for 循环只是跳过零。 var add
编辑:我已经在程序的输出中进行了编辑。 该程序要求估计给定值 mu。用户给出一个值 mu,同时还提供了四个不等于 1 的不同数字(称为 w、x、y、z)。然后,程序尝试使用 de Jaeger 公式找
我正在编写一个算法,该算法对一个整数数组从末尾到开头执行一个大循环,其中包含一个 if 条件。第一次条件为假时,循环可以终止。 因此,对于 for 循环,如果条件为假,它会继续迭代并进行简单的变量更改
现在我已经习惯了在内存非常有限的情况下进行编程,但我没有答案的一个问题是:哪个内存效率更高;- for(;;) 或 while() ?还是它们可以平等互换?如果有的话,还要对效率问题发表评论! 最佳答
这个问题已经有答案了: How do I compare strings in Java? (23 个回答) 已关闭 8 年前。 我正在尝试创建一个小程序,我可以在其中读取该程序的单词。如果单词有 6
这个问题在这里已经有了答案: python : list index out of range error while iteratively popping elements (12 个答案) 关
我正在尝试向用户请求 4 到 10 之间的整数。如果他们回答超出该范围,它将进入循环。当用户第一次正确输入数字时,它不会中断并继续执行 else 语句。如果用户在 else 语句中正确输入数字,它将正
我尝试创建一个带有嵌套 foreach 循环的列表。第一个循环是循环一些数字,第二个循环是循环日期。我想给一个日期写一个数字。所以还有另一个功能来检查它。但结果是数字多次写入日期。 Out 是这样的:
我想要做的事情是使用循环创建一个数组,然后在另一个类中调用该数组,这不会做,也可能永远不会做。解决这个问题最好的方法是什么?我已经寻找了所有解决方案,但它们无法编译。感谢您的帮助。 import ja
我尝试创建一个带有嵌套 foreach 循环的列表。第一个循环是循环一些数字,第二个循环是循环日期。我想给一个日期写一个数字。所以还有另一个功能来检查它。但结果是数字多次写入日期。 Out 是这样的:
我正在模拟一家快餐店三个多小时。这三个小时分为 18 个间隔,每个间隔 600 秒。每个间隔都会输出有关这 600 秒内发生的情况的统计信息。 我原来的结构是这样的: int i; for (i=0;
这个问题已经有答案了: IE8 for...in enumerator (3 个回答) How do I check if an object has a specific property in J
哪个对性能更好?这可能与其他编程语言不一致,所以如果它们不同,或者如果你能用你对特定语言的知识回答我的问题,请解释。 我将使用 c++ 作为示例,但我想知道它在 java、c 或任何其他主流语言中的工
这个问题不太可能帮助任何 future 的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visit
我是 C 编程和编写代码的新手,以确定 M 测试用例的质因数分解。如果我一次只扫描一次,该功能本身就可以工作,但是当我尝试执行 M 次时却惨遭失败。 我不知道为什么 scanf() 循环有问题。 in
这个问题已经有答案了: JavaScript by reference vs. by value [duplicate] (4 个回答) 已关闭 3 年前。 我在使用 TSlint 时遇到问题,并且理
我尝试在下面的代码中添加 foreach 或 for 循环,以便为 Charts.js 创建多个数据集。这将允许我在此折线图上创建多条线。 我有一个 PHP 对象,我可以对其进行编码以稍后填充变量,但
我是一名优秀的程序员,十分优秀!