Как оптимизировать цикл фильтрации для Cortex-M3? - PullRequest
1 голос
/ 17 апреля 2019

Мне просто нужно изменить код, чтобы он выполнял ту же базовую функцию, но более оптимизированную, в основном я думаю, что цикл фильтрации - это основной кусок кода, который можно изменить, так как я чувствую, что там слишком много инструкций, но не знаю с чего начать. Я работаю с Cortex M3 и Thumb 2.

Я попытался вмешаться в цикл фильтрации, чтобы я мог добавить предыдущее число, сохраненное в регистре, и разделить его на 8, но я не знаю, как на самом деле это выполнить.

; Perform in-place filtering of data supplied in memory
; the filter to be applied is a non-recursive filter of the form
; y[0] = x[-2]/8 + x[-1]/8 + x[0]/4 + x[1]/8 + x[2]/8

  ; set up the exception addresses
  THUMB
  AREA RESET, CODE, READONLY
  EXPORT __Vectors
  EXPORT Reset_Handler
__Vectors 
  DCD 0x00180000     ; top of the stack 
  DCD Reset_Handler  ; reset vector - where the program starts

num_words EQU (end_source-source)/4  ; number of input values
filter_length EQU 5  ; number of filter taps (values)

  AREA 2a_Code, CODE, READONLY
Reset_Handler
  ENTRY
  ; set up the filter parameters
  LDR r0,=source        ; point to the start of the area of memory holding inputs
  MOV r1,#num_words     ; get the number of input values
  MOV r2,#filter_length ; get the number of filter taps
  LDR r3,=dest          ; point to the start of the area of memory holding outputs

  ; find out how many times the filter needs to be applied
  SUBS r4,r1,r2   ; find the number of applications of the filter needed, less 1
  BMI exit        ; give up if there is insufficient data for any filtering

  ; apply the filter  
filter_loop
  LDMIA r0,{r5-r9}     ; get the next 5 data values to be filtered
  ADD r5,r5,r9         ; sum x[-2] with x[2]
  ADD r6,r6,r8         ; sum x[-1] with x[1]
  ADD r9,r5,r6         ; sum x[-2]+x[2] with x[-1]+x[1]
  ADD r7,r7,r9,LSR #1  ; sum x[0] with (x[-2]+x[2]+x[-1]+x[1])/2
  MOV r7,r7,LSR #2     ; form (x[0] + (x[-2]+x[-1]+x[1]+x[2])/2)/4
  STR r7,[r3],#4       ; save calculated filtered value, move to next output data item
  ADD r0,r0,#4         ; move to start of next 5 input data values
  SUBS r4,r4,#1        ; move on to next set of 5 inputs 
  BPL filter_loop      ; continue until last set of 5 inputs reached

  ; execute an endless loop once done 
exit    
  B exit

  AREA 2a_ROData, DATA, READONLY
source  ; some saw tooth data to filter - should blunt the sharp edges
  DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
  DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
  DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
  DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
end_source

  AREA 2a_RWData, DATA, READWRITE
dest  ; copy to this area of memory
  SPACE end_source-source
end_dest
  END
  END

Я ожидаю, что будет более эффективный способ выполнения кода, который уменьшит общий размер кода или ускорит время выполнения циклов, если он делает то же самое. Любая помощь будет оценена.

1 Ответ

2 голосов
/ 18 апреля 2019

Для размера кода попробуйте использовать только регистры r0..r7, которые можно использовать в коротких 16-битных кодировках.

Кроме того, версии инструкций с установкой флага часто имеют 16-битные кодировки, когда для версии без установки флага требуется 32-разрядная кодировка. например,

  • adds r0, #4 - 16-битный против 32-битного add r0, #4
  • movs r7,r7,LSR #2 - 16-битный по сравнению с 32-битным MOV r7,r7,LSR #2
  • movs r2,#filter_length является 16-разрядным по сравнению с 32-разрядным MOV r2,#filter_length. (не крошечные, такие как #88, все еще требуется 32-битный Thumb2 mov)
  • stmia r3!, {r5} (с обратной записью) - 16-разрядный по сравнению с 32-разрядным str r7, [r3], #4 с постинкрементным увеличением.

См. Раздел «Размер кода Thumb» моего ответа на ваш предыдущий вопрос: Как сократить время выполнения и количество циклов для факториального цикла? И / или размер кода? . Посмотрите на разборку вашего кода, найдите 32-битные инструкции, проверьте, почему они 32-битные, и найдите способ сделать их 16-битными. Это просто супер базовая оптимизация Thumb, которую вы всегда можете сделать.


r1 и r2 даже не используются внутри вашего цикла, а r4 = r1-r2 - это константа времени сборки, которую вы вычисляете во время выполнения с 3 инструкциями ... Так что это очевидно безумие против movs r4, #num_words - filter_length.

Если предполагается, что это входные данные, которые не известны во время сборки для вашего реального кода (возможно, одна и та же функция иногда используется на разных входах?), То повторно используйте регистры, которые являются «мертвыми» после вычисления счетчика цикла , Это немного неуклюже, когда вы принимаете указатели в r0 и r3, поэтому у вас есть r2 и r4-r7 бесплатно, если вы используете r1 для счетчика цикла, или r1-r2 и r5-r7 бесплатно, если вы используете r4.

Я решил использовать r1 для счетчика циклов. Это разборка с моей версии (arm-none-eabi-gcc -g -c -mthumb -mcpu=cortex-m3 arm-filter.S && arm-none-eabi-objdump -drwC arm-filter.o)

@@ Saving code size without any other changes

00000000 <function>:
   0:   480a            ldr     r0, [pc, #40]   ; (2c <exit+0x4>)
   2:   f05f 0158       movs.w  r1, #88 ; 0x58
   6:   2205            movs    r2, #5
   8:   4b09            ldr     r3, [pc, #36]   ; (30 <exit+0x8>)
   a:   1a89            subs    r1, r1, r2
   c:   d40c            bmi.n   28 <exit>

0000000e <filter_loop>:
   e:   e890 00f4       ldmia.w r0, {r2, r4, r5, r6, r7}
  12:   443a            add     r2, r7
  14:   4434            add     r4, r6
  16:   4414            add     r4, r2
  18:   eb15 0554       adds.w  r5, r5, r4, lsr #1
  1c:   08ad            lsrs    r5, r5, #2
  1e:   c320            stmia   r3!, {r5}
  20:   3004            adds    r0, #4
  22:   3901            subs    r1, #1
  24:   d5f3            bpl.n   e <filter_loop>

00000026 <exit>:
  26:   e7fe            b.n     26 <exit>

Cortex-M3 не имеет NEON, но между выходами есть повторное использование данных. С развертыванием мы можем определенно использовать результаты загрузки и некоторые из «внутренних» add результатов. Может быть, с помощью скользящего окна, чтобы вычесть слово, которое больше не является частью общей суммы, и добавить новое.

Но со средним элементом, являющимся «особым», у нас есть два двухэлементных окна с каждой стороны, , если у нас нет достаточных запасных бит в верхней части, чтобы добавить x[0] дважды, а затем сдвинуть вправо на 3 без переполнения . Тогда вам даже не нужно раскручивать, просто загрузите 1 элемент / отрегулируйте скользящее окно и пересчитайте средний / сохранить 1 элемент.

(Моя первая версия этого ответа была основана на неправильном понимании кода. Я мог бы обновить с оптимизацией скорости позже, но сейчас редактирование, чтобы удалить неправильные вещи.)

...