Ваш встроенный код сборка делает ряд ошибок:
- пытается использовать 64-разрядную структуру в качестве операнда с 32 (
"=r"
) ограничение. Это то, что дает вам ошибку.
- Он не использует, что выходной операнд в любом
- Он не говорит компилятору, где выход на самом деле представляет собой (S0/S1)
- Он не говорит компилятору, что
len
, как предполагается, вход
- Он сжимает несколько регистров, R3, S11, S12, S13, S14, S14, не сообщая компилятору.
- Он использует метку
.loop
, которая излишне запрещает компилятору вставлять код в несколько мест.
- На самом деле это не эквивалент кода C++, который вы указали, вместо этого вычисляете что-то другое.
Я не собираюсь объяснять, как вы можете исправить все эти ошибки, потому что вы shouldn't be using inline assembly. Вы можете написать свой код на C++ и позволить компилятору выполнять векторию.
Например компиляции следующий код, что эквивалентно вашему примеру C++ кода, с GCC 4.9 и -O3 -funsafe-math-optimizations
вариантов:
dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
int i;
dcmplx xout;
xout.re = xout.im = 0.0;
for (i = 0; i < len; i++) {
xout.re += hat[i].re * buf[i].re + hat[i].im * buf[i].im;
xout.im += hat[i].re * buf[i].im - hat[i].im * buf[i].re;
}
return xout;
}
генерирует следующий узел в качестве своего внутреннего цикла:
.L97:
add lr, lr, #1
cmp ip, lr
vld2.32 {d20-d23}, [r5]!
vld2.32 {d24-d27}, [r4]!
vmul.f32 q15, q12, q10
vmul.f32 q14, q13, q10
vmla.f32 q15, q13, q11
vmls.f32 q14, q12, q11
vadd.f32 q9, q9, q15
vadd.f32 q8, q8, q14
bhi .L97
основе ваш встроенный ассемблерный код, вероятно, что компилятор сгенерирован лучше, чем вы бы придумали, если бы попытались его векторизовать самостоятельно.
-funsafe-math-optimizations
необходим, поскольку инструкции NEON не полностью соответствуют стандарту IEEE 754.Как GCC documentation состояний:
If the selected floating-point hardware includes the NEON extension (e.g. -mfpu=‘neon’
), note that floating-point operations are not generated by GCC's auto-vectorization pass unless -funsafe-math-optimizations
is also specified. This is because NEON hardware does not fully implement the IEEE 754 standard for floating-point arithmetic (in particular denormal values are treated as zero), so the use of NEON instructions may lead to a loss of precision.
Следует также отметить, что компилятор генерирует почти так же хорошо, как код выше, если вы не свернуть свой собственный сложный тип, как в следующем примере:
#include <complex>
typedef std::complex<float> complex;
complex ComplexConv_std(int len, complex *hat, complex *buf)
{
int i;
complex xout(0.0f, 0.0f);
for (i = 0; i < len; i++) {
xout += std::conj(hat[i]) * buf[i];
}
return xout;
}
Одним из преимуществ, используя собственный тип, однако, является то, что вы можете улучшить код компилятор генерирует делаете одно небольшого изменения в том, как вы заявляете struct dcmplx
:
typedef struct {
float re;
float im;
} __attribute__((aligned(8)) dcmplx;
Говоря, что это должно быть 8-байтовое (64-битное) выровненное, это позволяет компилятору пропустить проверку, чтобы убедиться, что она соответствующим образом выровнена, а затем вместо этого вернется к более медленной скалярной реализации.
Теперь, предположительно, можно сказать, что вы были недовольны тем, как GCC векторизовал ваш код и думал, что вы можете сделать лучше. Это оправдывает использование встроенной сборки? Нет, следующий пример: ARM NEON intrinsics. Использование intrinics - это просто нормальное программирование на C++, вам не нужно беспокоиться о наборе специальных правил, которым вам нужно следовать. Например, вот как я преобразовал Векторизованных сборку выше в этот непроверенный код, который использует встроенные функции:
#include <assert.h>
#include <arm_neon.h>
dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
int i;
dcmplx xout;
/* everything needs to be suitably aligned */
assert(len % 4 == 0);
assert(((unsigned) hat % 8) == 0);
assert(((unsigned) buf % 8) == 0);
float32x4_t re, im;
for (i = 0; i < len; i += 4) {
float32x4x2_t h = vld2q_f32(&hat[i].re);
float32x4x2_t b = vld2q_f32(&buf[i].re);
re = vaddq_f32(re, vmlaq_f32(vmulq_f32(h.val[0], b.val[0]),
b.val[1], h.val[1]));
im = vaddq_f32(im, vmlsq_f32(vmulq_f32(h.val[1], b.val[1]),
b.val[0], h.val[0]));
}
float32x2_t re_tmp = vadd_f32(vget_low_f32(re), vget_high_f32(re));
float32x2_t im_tmp = vadd_f32(vget_low_f32(im), vget_high_f32(im));
xout.re = vget_lane_f32(vpadd_f32(re_tmp, re_tmp), 0);
xout.im = vget_lane_f32(vpadd_f32(im_tmp, im_tmp), 0);
return xout;
}
Наконец, если это не было достаточно хорошо, и вам нужно настроить каждую часть производительности вы могли бы тогда это еще не хорошая идея использовать встроенную сборку. Вместо этого в вашем последнем случае следует использовать обычную сборку. Поскольку вы переписываете большую часть функции в сборке, вы можете полностью ее записать в сборке. Это означает, что вам не нужно беспокоиться о том, чтобы сообщить компилятору обо всем, что вы делаете в встроенной сборке. Вам нужно только соответствовать ARM ABI, который может быть достаточно сложным, но намного проще, чем все правильно с встроенной сборкой.
Здесь нет причин использовать встроенную сборку. Просто используйте простой C++. Вы также должны рассмотреть возможность использования 'std :: complex' вместо вашего собственного сложного типа. –
Использование inline asm обычно является [плохой идеей] (https://gcc.gnu.org/wiki/DontUseInlineAsm). Тем не менее, вы не присваиваете значение% 0 в этом коде, поэтому содержимое xout не определено. Для вывода структур вы рассматривали передачу 'dcmplx *'? –
@RossRidge, причина использования встроенной сборки здесь: arm-gcc генерирует только код, использующий vfp, а не регистр неонов (я проверил). Я даже попробовал-векторизовать. Я добавлю оригинальный код для просмотра. Встроенная сборка, хотя это vfp-код, потому что я новичок в этой поданной и «скопирую» код, сгенерированный gcc, чтобы увидеть, работает ли он. В конце концов я перейду к неоновому коду. –