2016-09-22 8 views
1

Я пытаюсь оптимизировать следующий код complex.cpp:ARM код встроенный ассемблер с ошибкой «невозможно ограничение в ассемблере»

typedef struct { 
    float re; 
    float im; 
} dcmplx; 

dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf) 
{ 
    int i; 
    dcmplx z, xout; 

    xout.re = xout.im = 0.0; 
    asm volatile (
    "movs r3, #0\n\t" 
    ".loop:\n\t" 
    "vldr s11, [%[hat], #4]\n\t" 
    "vldr s13, [%[hat]]\n\t" 
    "vneg.f32 s11, s11\n\t" 
    "vldr s15, [%[buf], #4]\n\t" 
    "vldr s12, [%[buf]]\n\t" 
    "vmul.f32 s14, s15, s13\n\t" 
    "vmul.f32 s15, s11, s15\n\t" 
    "adds %[hat], #8\n\t" 
    "vmla.f32 s14, s11, s12\n\t" 
    "vnmls.f32 s15, s12, s13\n\t" 
    "adds %[buf], #8\n\t" 
    "vadd.f32 s1, s1, s14\n\t" 
    "vadd.f32 s0, s0, s15\n\t" 
    "adds r3, r3, #1\n\t" 
    "cmp r3, r0\n\t" 
    "bne .loop\n\t" 
    : "=r"(xout) 
    : [hat]"r"(hat),[buf]"r"(buf) 
    : "s0","cc" 
    ); 
    return xout; 
} 

Когда он скомпилирован с «Арм-линукс-gnueabihf-г ++ -c комплекса .cpp -o complex.o -mfpu = neon ", Я получил следующую ошибку: невозможное ограничение в 'asm'.

Когда я комментирую «= r» (xout), компилятор не жалуется, но как я могу получить результат регистрации 's0' в xout?

Кроме того, как это работает, если r0 содержит возвращаемое значение, но тип возврата является сложной структурой, поскольку r0 является только 32-битным? регистр.

Оригинала с кодом я отправляю здесь:

dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf) 
{ 
    int i; 
    dcmplx z, xout; 
    xout.re = xout.im = 0.0; 
    for(int i = 0; i < len; i++) { 
     z = BI_dcmul(BI_dconjg(hat[i]),buf[i]); 
     xout = BI_dcadd(xout,z); 
    } 
    return xout; 
} 
dcmplx BI_dcmul(dcmplx x, dcmplx y) 
{ 
    dcmplx z; 
    z.re = x.re * y.re - x.im * y.im; 
    z.im = x.im * y.re + x.re * y.im; 
    return z; 
} 
dcmplx BI_dconjg(dcmplx x) 
{ 
    dcmplx y; 
    y.re = x.re; 
    y.im = -x.im; 
    return y; 
} 
dcmplx BI_dcadd(dcmplx x, dcmplx y) 
{ 
    dcmplx z; 
    z.re = x.re + y.re; 
    z.im = x.im + y.im; 
    return z; 
} 
+0

Здесь нет причин использовать встроенную сборку. Просто используйте простой C++. Вы также должны рассмотреть возможность использования 'std :: complex' вместо вашего собственного сложного типа. –

+0

Использование inline asm обычно является [плохой идеей] (https://gcc.gnu.org/wiki/DontUseInlineAsm). Тем не менее, вы не присваиваете значение% 0 в этом коде, поэтому содержимое xout не определено. Для вывода структур вы рассматривали передачу 'dcmplx *'? –

+0

@RossRidge, причина использования встроенной сборки здесь: arm-gcc генерирует только код, использующий vfp, а не регистр неонов (я проверил). Я даже попробовал-векторизовать. Я добавлю оригинальный код для просмотра. Встроенная сборка, хотя это vfp-код, потому что я новичок в этой поданной и «скопирую» код, сгенерированный gcc, чтобы увидеть, работает ли он. В конце концов я перейду к неоновому коду. –

ответ

6

Ваш встроенный код сборка делает ряд ошибок:

  • пытается использовать 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, который может быть достаточно сложным, но намного проще, чем все правильно с встроенной сборкой.

+1

Вы можете «typedef __attribute __ ((aligned (8))) std :: complex aligned_complex', чтобы убедиться, что компилятор знает, что вы хотите передать ориентированные объекты. –

 Смежные вопросы

  • Нет связанных вопросов^_^