При переходе по массиву с встроенной сборкой следует использовать модификатор регистра "r" или модификатор памяти "m" ?
Рассмотрим пример, который добавляет два массива с плавающей запятой x
и y
и записывает результаты в z
. Обычно я бы использовал intrinsics, чтобы сделать это, как это
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);
}
Вот встроенное решение сборки, которое я придумал с помощью модификатора регистра "r"
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)
:
);
}
}
Это создает аналогичную сборку для GCC. Основное различие заключается в том, что GCC добавляет 16 в индексный регистр и использует шкалу 1, тогда как встроенное решение сборки добавляет 4 в индексный регистр и использует шкалу из 4.
Я не смог использовать общий регистр для итератора. Я должен был указать тот, который в этом случае был rax
. Есть ли причина для этого?
Вот решение, которое я придумал с помощью модификатора памяти "m"
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])
:
);
}
}
Это менее эффективно, так как он не использует индексный регистр и вместо этого должен добавить 16 в базовый регистр каждого массива. Сгенерированная сборка (gcc (Ubuntu 5.2.1-22ubuntu2) с 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
Есть ли лучшее решение, использующее модификатор памяти "m" ? Есть ли способ заставить его использовать индексный регистр? Причина, по которой я спросил, это то, что мне было более логично использовать память "m" памяти, так как я читаю и записываю память. Кроме того, с модификатором регистра "r" я никогда не использую список выходных операндов, который сначала казался мне странным.
Может быть, есть лучшее решение, чем использование "r" или "m" ?
Вот полный код, который я использовал для проверки этого
#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("");
}