Как уменьшить время выполнения и количество циклов для факториального цикла?И / или размер кода? - PullRequest
2 голосов
/ 09 апреля 2019

В основном мне трудно получить время выполнения чуть меньше, чем оно есть, а также уменьшить количество тактов и объем памяти.У кого-нибудь есть идеи о том, как я могу это сделать?Код работает нормально, я просто хочу немного его изменить.

Написал рабочий код, но не хочу испортить код, но также не знаю, какие изменения внести.

; Calculation of a factorial value using a simple loop

; 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

AREA 2a_Code, CODE, READONLY
Reset_Handler
ENTRY
start   
MOV r1,#0    ; count the number of multiplications performed 
MOV r2,#3    ; the final value in the factorial calculation
MOV r3,#1    ; the factorial result will be stored here

; loop r2 times forming the product  
fact
ADD r1,r1,#1  ; find the next multiplicand
MUL r3,r1,r3  ; form the next product - note that MUL r3,r3,r1 gives unpredictable output
CMP r1,r2     ; check if the final value has been reached
BMI fact      ; continue if all products have not been formed

exit    ; stay in an endless loop 
B exit
END

Текущие результаты: Размер памяти: 0x00000024 Циклы синхронизации: 22 Общее время выполнения: 1.1 Микросекунды

Мы работаем с Cortex M3

Мне просто нужен любой из этихчтобы быть уменьшенным, изменения в коде могут быть незначительными, пока это приводит к различным результатам.

Ответы [ 4 ]

5 голосов
/ 10 апреля 2019

Часто размер кода и производительность являются компромиссом.Развертывание цикла часто помогает повысить производительность (по крайней мере для больших входов), но требует дополнительной логики вне цикла для обработки очистки и т. Д.


Большая часть этого ответа предполагала более высокую производительность ЦП, такую ​​какCortex-A9 или Cortex-A53, где программный конвейер для создания параллелизма на уровне команд был бы полезен.Cortex M3 является скалярным и содержит односциклическую инструкцию умножения, что значительно упрощает оптимизацию для.

(В первоначальном вопросе не было указано ядро, и я ожидал, что даже младшие процессоры будут иметьмногоцикловая mul задержка. Я нашел только числа Cortex-M3 после того, как записал их.)

Ваш код, вероятно, станет узким местом для задержки целочисленного умножения .В отличие от add, где результат будет готов в следующем цикле, mul является сложным и требует нескольких циклов для получения результата.

(За исключением некоторых очень медленно синхронизируемых чипов, таких как, по-видимому, Cortex-M3имеет 1-тактную инструкцию mul. Но Cortex-M0 / M0 + / M23 доступны с выбором производительности 1 цикла или 32 цикла для этой инструкции! Медленная итерация = меньший кремний.)


Сам модуль умножения выполнения часто конвейеризуется, поэтому несколько независимых * умножений могут быть запущены одновременно, но ваш факторный цикл нуждается в каждом результате умножения в качестве входных данных для следующей итерации.(Только для высокопроизводительных ядер, но не для серии Cortex-M. 32-тактное умножение на медленных чипах Cortex-M является итеративным и, по-видимому, не конвейерным, поэтому другое умножение не может начаться во время работы, и не будет никакой выгодычтобы разоблачить любой параллелизм на уровне команд, кроме сокращения издержек цикла.)

Обратите внимание, что умножение ассоциативно: 1 * 2 * 3 = 3 * 2 * 1, поэтому мы можем отсчитывать от n, как указывает ответ @ ensc.Или (1*2) * (3*4) = 1*2*3*4.

Вместо этого мы можем сделать 1 * 2 * ... * (n/2) параллельно с n/2+1 * n/2+2 * n/2+3 * ... * n, чередуя работу с этими двумя цепочками зависимостей. Или мы можем чередовать 1 * 3 * 5 * ... * nс 2 * 4 * 6 * ... n-1, в цикле, который сделал n -= 2 и вычисляет n+1 из этого.(Затем, в конце, вы умножаете эти 2 продукта).

Это, очевидно, потребует большего размера кода, но может значительно повысить производительность.


Конечно,таблица поиска является еще одним обходным путем.Если вы заботитесь только о входных данных, которые не переполняют 32-битный результат, это довольно маленькая таблица.Но это требует значительных затрат.


Даже на обычном процессоре (где выполнение инструкции должно начинать в порядке программы), долго выполняющиеся инструкции, такие как кеш-пропущенныеДля загрузки или умножения может быть разрешено завершить не по порядку, поэтому, например, некоторые add инструкции могут выполняться после запуска mul, но до того, как результат mul будет записан обратно.Или даже запускать другую независимую mul инструкцию в тени более ранней mul задержки.

Я набрал в Google некоторые показатели производительности ARM, чтобы, возможно, почувствовать, что типично.

ДляНапример, Cortex-A9 является старым, довольно распространенным высокопроизводительным процессором ARMv7 высокого уровня, который является суперскалярным (несколько инструкций за цикл) с выполнением вне порядка.

mul "берет" 2 цикла и имеет задержку результата 4 цикла .Они не объясняют, что они подразумевают под стоимостью без задержек.Возможно, это обратная пропускная способность исполнительного блока, например, как часто вы можете начать новую независимую операцию.Это неработоспособный процессор, поэтому нет смысла останавливать другие инструкции на 2 цикла.В разделе NEON SIMD они объясняют то, что выглядит как одно и то же число "циклов":

Это число циклов выдачи, которое выполняет конкретная инструкция, и является абсолютнымминимальное количество циклов в команде, если нет блокировки операндов.

(блокировка операнда = ожидание готовности входного операнда, если более ранняя инструкция еще не дала результата).

(Cortex-A9 поддерживает упакованное целочисленное умножение, поэтому для больших факториалов выможно посмотреть на 4 умножения параллельно, начиная один вектор на 4 цикла, используя vmul.32 q1, q1, q2. Или 2 на 2 цикла с 64-битными d регистрами, но тогда вам понадобится больше vaddинструкции и в отличие от умножения, vadd.32 так же быстро с 128-битными q регистрами, как и с 64-битными векторами.Таким образом, SIMD может дать вам удвоенную пропускную способность скаляра в Cortex-A9, если вы используете достаточно регистров, чтобы скрытьбольшая задержка. Но SIMD, вероятно, будет полезна только с n, настолько большим, что n! переполняет 32-разрядное целое число, так что вы получите результат по модулю 2 ^ 32.)


меньшая задержкаИнструкции умножения ARM:

mul - это 32x32 => 32-битное умножение.На Cortex-A9 он имеет пропускную способность 2c и задержку 4c.

(muls - это 16-битная инструкция в режиме большого пальца, и ее следует использовать, если вам не нужно блокировать флаги. mul inРежим Thumb доступен только в ARMv6T2 и более поздних версиях.)

smulbb - это умножение со знаком 16x16 => 32-битный , которое считывает только младшую половину еговходы, но имеет пропускную способность 1c и задержку 3c на A9 .(BB = bottom, bottom. Другие комбинации также доступны, наряду с многократным накоплением и различными прикольными вещами.)

Не существует 2-байтовой версии Thumb для smulxy, так что это хуже для кода-size чем muls.

К сожалению, smulxy недоступен в неподписанной версии, так что ограничивает диапазон входов, с которыми мы можем его использовать, положительным int16_t, а не uint16_t.

Но если мы заботимся только о случае, когда конечный 32-битный результат не переполняется, мы можем упорядочить наш порядок операций так, чтобы последнее умножение имело 2 входа одинаковой величины (оба 16-битные с большим числом элементов)номера).то есть как можно ближе к sqrt(n!).Так, например, произведение шансов и четностей было бы разумным, но (n-1)! * n было бы наихудшим случаем, потому что для этого требовалось бы (n-1)! для размещения в 16 битах.На самом деле наихудший случай - обратный отсчет с n, поэтому последний умножается на 3, а затем на 2. Мы могли бы в особом случае умножить на 2 смещение влево ...


Поместив этисоединяя вместе, обратите внимание, что умножение на 1 не допускается (за исключением smulbb, где оно усекает ввод до 16 бит).Таким образом, мы можем развернуть таким способом, который останавливается после умножения на 1 или 2, в зависимости от того, является ли ввод нечетным или четным.

Таким образом, вместо того, чтобы знать, что нечетно, а что чётно, мы просто имеем lon-1) и hi (начиная с n).

;; UNTESTED, but it does assemble with the GNU assembler, after sed -i 's/;/@/' arm-fact.S
;; and replacing THUMB with
; .thumb
; .syntax unified
THUMB

;; Input: n in r0.   (n is signed positive, otherwise we return n.)
;; Output: n! in r0.
;; clobbers: r1, r2, r3
;; pre-conditions: n! < 2^31.  Or maybe slightly lower.
fact:
    subs   r3, r0, #3   ; r3 = lo = n-3  (first multiplier for loprod)
    bls   .Ltiny_input
    subs   r2, r0, #2   ; r2 = hi = n-2  (first multiplier for hiprod)
    subs   r1, r0, #1   ; r1 = loprod = n-1
                        ; r0 = hiprod = n

.Lloop:                 ; do {
    smulbb  r0,r0, r2      ; hiprod *= hi
    subs    r2, #2         ; hi -= 2 for next iter
    smulbb  r1,r1, r3
    subs    r3, #2         ; lo -= 2 for next iter
    bgt     .Lloop       ; while((lo-=2) > 0);  signed condition
    ; r3 = 0 or -1, r2 = 1 or 0.  The last multiplies were:
    ;       hiprod *= 2 and loprod *= 1  for even n
    ;   or  hiprod *= 3 and loprod *= 2  for odd n

    ; muls  r0, r1
    smulbb  r0,r0, r1      ; return  hiprod *= loprod

    bx lr    ; or inline this

.Ltiny_input:   ; alternate return path for tiny inputs
    ; r0 = n.   flags still set from  n - 3
    IT eq                  ; GAS insists on explicit IT for thumb mode
    moveq   r0, #6         ; 3! = 6, else n! = n for smaller n=1 or 2.
                           ; 0! = 1 case is not handled, nor are negative inputs
    bx lr

(. L в имени метки делает ее локальной меткой, которая не отображается в объектном файле, по крайней мере, в GASСинтаксис. Может быть, не в ARMASM, если вы используете этот ассемблер.)

Сборка ARM позволяет вам исключить место назначения, когда оно совпадает с первым источником, для некоторых инструкций, таких как subs, но не smulbb.Вы можете написать это как subs r2, r2, #2 каждый раз, если хотите.

Вы можете использовать muls r0, r1 для конечного продукта , потому что окончательный hiprod немного выше, чем loprod.Продукт может не переполниться, даже если hiprod> max int16_t.Это также сэкономит 2 байта размера кода, но добавит 1 цикл задержки в Cortex-A9.(Кстати, ARMv6 исправил «непредсказуемый результат» со странной mul d,d, src, и ваш код использовал 32-битные инструкции Thumb2, поэтому он все равно работает только на ARMv6T2 и выше.)


С2 аккумулятора для продуктов, это может работать на 2 умножения на 3 цикла на Cortex-A9 , в значительной степени в зависимости от микроархитектуры ЦП и от того, выдержит ли его интерфейс.На ARM в порядке, я бы беспокоился, что он сможет запускать другие инструкции до того, как умножение закончится.

Может быть лучше потратить 2 дополнительных байта на sub вместо subs, чтобы мы могли вычислить флаги за пару инструкций перед ветвью , возможно, уменьшив штраф за неправильный прогноз ветки и избежав остановок на в порядке процессоров. smulbb не затрагивает флаги, поэтому мы можем сначала сделать loprod и иметь hi материал, а не сенсорные флаги.

.loop:                  ; do {
    smulbb  r1, r3       ; loprod *= lo
    subs    r3, #2       ; lo -= 2 for next iter, and set flags
    smulbb  r0, r2       ; hiprod *= hi
    sub     r2, #2       ; hi -= 2 for next iter (no flags)
    bgt     .loop       ; while((lo-=2) >= 0);

Обратите внимание, что мы изменяем r3 и r2 вправо после того, как smulbb читает их, избегая создания задержки для зависимости данных от чипов в порядке.


Вы используете режим Thumb и оптимизируете под размер кода, поэтому важно знать, какие формы каких инструкций могут использовать 2-байтовое / 16-битное кодирование, а какие доступны только как 32-битный Thumb2 кодировок.

subs Rd, Rn, #imm может быть закодирован как 16-битная инструкция Thumb для imm = 0..7 (3-битная немедленная). Или с тем же регистром, что и src и destination, для imm = 0..255. Так что мои инструкции по копированию и замене компактны.

Установка без флага sub не может быть 16-битной инструкцией, кроме как внутри блока IT, или с SP в качестве операнда.

Предсказанные инструкции в режиме Thumb , как и moveq r0, #6, требуют, чтобы ассемблер использовал инструкцию IT , чтобы ввести предикацию для следующих до 4 инструкций. В режиме ARM старшие 4 бита каждой команды сигнализируют о предикации. (Если вы не используете суффикс, ассемблер кодирует его как ALways, т. Е. Без предиката.)

Мы могли бы обработать n==0 случай с другими 4 или 6 байтами , с cmp r0,#0 / moveq r0, #1. Возможно, уменьшив его до 4 байтов, если мы поместим tst / mov в один и тот же IT-блок ИТ-специалист не делает снимок фактического состояния флага, он снимает предикаты, поэтому инструкции по установке флага внутри ИТ-блока могут влиять на более поздние инструкции в том же блоке. (Я думаю, что это правильно, но я не уверен на 100%).

tiny_input:    ; r0 = n,  flags set according to n-3
    ITET EQ
    moveq  r0, #6
    cmpne  r0, #0
    moveq  r0, #1

Или есть 16-бит cbnz, чтобы условно перепрыгнуть через mov r0, #1. Но цель ветвления должна быть от 4 до 130 байт после cbnz, поэтому мы не можем перепрыгнуть через одну 16-битную инструкцию, по-видимому!


Размер кода для моей версии:

$ arm-none-eabi-gcc -g -c -mcpu=cortex-a9 arm-fact.S
$ arm-none-eabi-objdump -drwC arm-fact.o 

arm-fact.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <fact>:
   0:   1ec3            subs    r3, r0, #3
   2:   d90b            bls.n   1c <.tiny_input>
   4:   1e82            subs    r2, r0, #2
   6:   1e41            subs    r1, r0, #1

00000008 <.loop>:
   8:   fb10 f002       smulbb  r0, r0, r2
   c:   3a02            subs    r2, #2
   e:   fb11 f103       smulbb  r1, r1, r3
  12:   3b02            subs    r3, #2
  14:   dcf8            bgt.n   8 <.loop>
  16:   fb10 f001       smulbb  r0, r0, r1
  1a:   4770            bx      lr

0000001c <.tiny_input>:
  1c:   bf08            it      eq
  1e:   2006            moveq   r0, #6
  20:   4770            bx      lr

Так что для этой функции это 0x22 байта. (Или 0x26, если мы хотим обработать 0! = 1.)

Это больше, чем ваша версия (ваш счетчик байтов включает в себя некоторые константы в памяти и инструкции mov для получения ввода), но теоретически может быть лучше, чем в два раза быстрее для большого ввода, на процессорах с конвейерными множителями). И, возможно, намного быстрее для входов от 1 до 3, когда он просто разветвляется и выдает результат.


У вас, вероятно, нет ничего похожего на Cortex-A9, потому что ваши 1,1 микросекунды = 22 тактовых такта означают тактовую частоту 20 МГц , в то время как Cortex-A9 был доступен в частотах от 0,8 до 2 ГГц.

Так, может быть, у вас есть гораздо более простое ядро, например Cortex M3 ? M3 поддерживает инструкцию mul и режим Thumb2. И в Википедии сказано, что их умножение составляет 1 цикл! Так что это странно, я удивлен, что у него есть такой эффективный множитель. Или просто из-за того, что он синхронизируется настолько медленно, что на 1 этапе есть время для многих задержек затвора, а это всего лишь 3-этапный конвейер.


Cortex-M3 версия:

sub и muls являются однотактными на Cortex-M3. Я не нашел числа отказов на ветках, но они распространены, поэтому я предполагаю, что это, вероятно, 1 цикл и не вызывает большого пузыря извлечения (если правильно предсказано ...). В HTML-руководстве по Cortex-M3 есть раздел Переадресация ветвей цели , который, как представляется, предназначен для уменьшения пузыря извлечения.

Его таблица хронирования команд показывает b<cond> стоит 1 цикл для не взятых или 2 цикла для взятых. (1 для ответвления, 1 для перезагрузки трубопровода после немедленного перемещения.). Таким образом, взятые ветки медленнее по сравнению с sub / mul, и развертывание было бы полезно, поэтому мой код, приведенный выше, все еще должен работать хорошо. (Но многократные аккумуляторы продукта не нужны, поэтому это может быть упрощено).

Оптимизация под размер кода:

;; UNTESTED
THUMB

;; Input: n in r0.   (n is signed positive, otherwise we return n.)
;; Output: n! in r0.
;; clobbers: r1
fact:
    subs   r1, r0, #1     ; i = n-1
    bls   .Ltiny_input    ; jump if n<=1

.Lloop:                 ; do {
    muls    r0, r1         ; prod *= i
    subs    r1, #1         ; --i
    bgt     .Lloop      ; while(--i > 0);  signed condition
    ; r1 = 0, r0 = n! 
    ; last multiply was a redundant prod *= 1 but avoiding that would take a cmp
.Ltiny_input:   ; alternate return path for tiny inputs
    ; 0! = 1 case is not handled, nor are negative inputs


    bx lr    ; or inline this

Я думаю, что это самое маленькое, что мы можем сделать. Цикл содержит 3 инструкции и, вероятно, стоит 4 цикла на одну итерацию (1 + 1 + 2, взятая ветвь стоит 2 цикла).

00000000 <fact>:
   0:   1e41            subs    r1, r0, #1
   2:   d902            bls.n   a <fact+0xa>
   4:   4348            muls    r0, r1
   6:   3901            subs    r1, #1
   8:   dcfc            bgt.n   4 <fact+0x4>
   a:   4770            bx      lr           # don't count this if inlining

Так что это 0xa = 10 байт, не считая инструкции bx lr return.

Мы можем обработать случай 0! = 1 с блоком IT после первого subs, до ветви , поэтому мы все еще можем перейти сразу после цикла (вместо отдельного блока, как моя версия Cortex-A9). Вы можете использовать этот трюк для этого тоже, хотя.

    subs   r1, r0, #1     ; i = n-1
    it lt
    movlt  r0, #1         ; n = 1 for  n<1
    bls   .Ltiny_input    ; return n if n was <=1

Если бы нам потребовался больший диапазон для ветви, мы могли бы использовать itt ls / movls r0, #1, чтобы ветвь находилась внутри блока IT (где инструкции ветви могут использовать кодировку, которая тратит больше битов на смещение и ни одного на предикат ). Но в данном случае это короткий диапазон, поэтому я решил оставить r0 неизменным в случае r0 == 1. Я не знаю, есть ли какие-либо ЦП, где более эффективная или более низкая задержка для предикативной инструкции, чтобы быть NOP вместо выполнения, но может быть.


Без развертывания, добавление cmp в цикл, чтобы избежать последней итерации *=1, обойдется нам в дополнительный цикл за итерацию (4 цикла вместо 3), поэтому платите только за себя с n=2 или, возможно, n=3.

Развертывание может значительно увеличить скорость при больших входах: от 1 муль на 3 цикла до асимптотического приближения к 1 муль на 2 цикла (суб + муль + амортизированные издержки цикла). Я не вижу способа избежать инструкции типа sub или mov для генерации отдельного ввода для каждого mul, за исключением жесткого кодирования последовательностей особых случаев для каждого n (например, *2 * 4 = *8 = сдвиг влево на 3), когда вы могли бы просто жестко закодировать ответ.

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

Если TL; DR, то переходите к концу для линии удара.

Пробовал это на синей таблетке STM32, STM32F103C8T6

Определенно ожидайте, что результаты изменятся с разными чипами, даже если они имеют одинаковую частоту вращения cortex-m3, поскольку процессор - это одно, а то, что питает его, и то, как другое, и это зависит от поставщика. Также иногда производитель чипов может компилировать ядро ​​по-разному, иногда он может иметь многоцикловые умножения, чтобы сэкономить на стоимости чипа, некоторые ядра, которые он может выбрать между выборкой 16 битов за раз или 32. Тесты часто легко проверять, поэтому берите их недоверчиво.

Я видел, что выполнение в sram было быстрее, чем во flash. ST, хотя, иногда нет, я не думаю, что на этих древних cortex-m3s у них есть свой (инструктивный) кеш с каким-то причудливым названием. Более новые делают, и вы не можете выключить его.
Другие производители микросхем не имеют этого и будут использовать ядра, которые поддерживают его, реализующие кэши вооружений, а не свои собственные (или не имеющие ни одного). Возможно, почему первые два эксперимента, приведенные ниже, выполняются в разное время (двузначное число впереди - шестнадцатеричное, счетчик таймера синцикла, cvr-адрес синусика передается в r0. Вы можете видеть, что я использовал nop, чтобы изменить выравнивание цикла. Документация arm не указала в обычном месте, что cortex-m3 извлекает полуслова или слова, но документация ST при разговоре о чем-то еще указывает выборки слов. Ваш цикл из четырех инструкций состоит из 2 слов, но выровненный не по границе слова означает, что он должен извлекать три слова в цикле. Если эти четыре слова выровнены, то нужно извлечь два слова в цикле, что позволит Питеру или кому-то еще посчитать инструкции для этого / вашего кода. Я уверен, что это фактор, но, возможно, есть и другие, вероятно нет.

Для этого чипа, работающего на флэш-памяти, гораздо быстрее. Вы можете увидеть эффект отключения предварительной выборки ST и добавления состояний ожидания.

000 Zero wait state, if 0 < SYSCLK≤ 24 MHz
001 One wait state, if 24 MHz < SYSCLK ≤ 48 MHz
010 Two wait states, if 48 MHz < SYSCLK ≤ 72 MHz

Так что, пока я работаю на внутренних часах с частотой 8 МГц, здесь есть два измерения, одно из которых - это количество часов, которое требуется, чтобы что-то сделать, если мы увеличим sysclk до 24 МГц, количество часов не должно измениться. Продолжительность настенных часов каждого цикла sysclk составляет треть времени, поэтому время настенных часов меньше. Производительность в реальном времени лучше. Следуя этим правилам, перейдите на один шаг выше 24 МГц, и теперь вы добавляете состояние ожидания, и ваш код снова замедляется. Поскольку количество системных часов для запуска кода теперь замедлилось. Теперь, если вы удвоите это до 48 МГц, это преодолело состояние ожидания? Вероятно, но для каждой программы / цикла существует точка между 24 МГц + полоса пропускания и 48 МГц, которые улавливаются прямо при производительности 24 МГц. И 48 МГц плюс чуть-чуть, теперь вы снова замедляетесь и где-то между 48 МГц плюс чуть-чуть 72 МГц, мы надеемся догнать и пропустить производительность 48 МГц.

Точно так же, как флэш-память не может работать, у других периферийных устройств есть правила, особенно с этими старыми чипами, такими как многие из основанных на Cortex-M3, есть другие проблемы производительности, с которыми вы падаете, некоторые периферийные устройства не могут работать так же быстро, как и любой другой sysclk. это значит, что у вас может быть какая-то другая скорость X, где вы находитесь на максимальной скорости для одной / некоторых ваших периферийных устройств или периферийных шин, и X + означает, что вам придется сократить время вдвое, так как это ваш самый маленький делитель теперь ваших периферийных устройств и / или их шины теперь в два раза медленнее, поэтому производительность вашего кода падает со скалы, возможно, хуже, чем половина. Этот ваш код не касается периферийных устройств. Он использует умножение, которое рискованно для производительности, но для cortex-m3 я не видел, чтобы была опция времени компиляции для одного цикла против другого, он просто сказал один цикл.

Питер рассмотрел очевидную оптимизацию, когда вы рассчитываете до некоторого числа, если позволяет набор инструкций, и ваш код, что он делает в этом случае, потому что a * b * c = c * b * a, так что вы хотитевести обратный отсчет и использовать флаги для сравнения с нулем или плюс минус, если это плавает на вашей лодке, а не с приращением, а затем необходимо выполнить сравнение перед условным.Когда вы пропустите до конца, вы увидите, что это было быстрее (меньше часов).

У M3 нет кэшей, у m4s и m7s.Таким образом, выполнение этого кода с его небольшим циклом потребует многократного выполнения цикла и цикла, чтобы увидеть влияние кэширования и выравнивания строк кэша и тому подобное.Но для m3 хорошо пройти один раз (если у чипа нет скрытого кэша, которым вы не можете управлять).

Меня действительно интересует только цикл, так как он имеет наибольший потенциал для циклических краж.Проверка / ограничение ввода, проверка ярлыков, поиск переполнения при умножении и т. Д., А это не то, о чем беспокоится этот ответ.

Я рекомендую вам поискать книги Майкла Абраша в Google.Например, Zen of Assembly, которую вы можете создать на github.Я прочитал его, когда он вышел, и я в значительной степени использовал то, чему научился там с тех пор, отлаживая чипы, инструменты, ломая вещи, улучшая производительность и т. Д. 8088/86 устарел, когда вышел, и если вы думаете, что это книга x86Вы полностью упускаете суть.Например, мое предположение о sram будет быстрее, не произошло здесь.Я также пробовал такие вещи, как добавление nops (дополнительных инструкций) внутри цикла, верите или нет, бывают моменты, когда это может повысить производительность цикла.Эти короткие конвейеры, небольшие процессоры предварительной выборки, хотя, как правило, это не так.

Иногда вы можете получить бесплатные инструкции в цикле, количество часов одинаково даже с большим количеством инструкций.Например, если это умножилось на несколько часов, в зависимости от количества часов и от того, к каким регистрам / ресурсам вы прикоснулись, вы можете получить несколько бесплатных инструкций в этом цикле.Похоже, что это умножение на один цикл, поэтому не могу надеяться на это здесь.

Затем есть материал, который вы читаете в учебниках Паттерсона и Хеннесси.Какие регистры вы выберете, могут повлиять на производительность.Порядок инструкций, если вы можете функционально перестроить инструкции и т. Д.

Заметки, сделанные в ходе простых экспериментов

15
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   6804        ldr r4, [r0, #0]

20000022 <fact_loop>:
20000022:   3101        adds    r1, #1
20000024:   434b        muls    r3, r1
20000026:   4291        cmp r1, r2
20000028:   d4fb        bmi.n   20000022 <fact_loop>
2000002a:   6805        ldr r5, [r0, #0]
2000002c:   1b60        subs    r0, r4, r5
2000002e:   bc30        pop {r4, r5}
20000030:   4770        bx  lr



12
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   6804        ldr r4, [r0, #0]

20000024 <fact_loop>:
20000024:   3101        adds    r1, #1
20000026:   434b        muls    r3, r1
20000028:   4291        cmp r1, r2
2000002a:   d4fb        bmi.n   20000024 <fact_loop>
2000002c:   6805        ldr r5, [r0, #0]
2000002e:   1b60        subs    r0, r4, r5
20000030:   bc30        pop {r4, r5}
20000032:   4770        bx  lr





15
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   46c0        nop         ; (mov r8, r8)
20000024:   6804        ldr r4, [r0, #0]

20000026 <fact_loop>:
20000026:   3101        adds    r1, #1
20000028:   434b        muls    r3, r1
2000002a:   4291        cmp r1, r2
2000002c:   d4fb        bmi.n   20000026 <fact_loop>
2000002e:   6805        ldr r5, [r0, #0]
20000030:   1b60        subs    r0, r4, r5
20000032:   bc30        pop {r4, r5}
20000034:   4770        bx  lr
20000036:   46c0        nop         ; (mov r8, r8)


12
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   46c0        nop         ; (mov r8, r8)
20000024:   46c0        nop         ; (mov r8, r8)
20000026:   6804        ldr r4, [r0, #0]

20000028 <fact_loop>:
20000028:   3101        adds    r1, #1
2000002a:   434b        muls    r3, r1
2000002c:   4291        cmp r1, r2
2000002e:   d4fb        bmi.n   20000028 <fact_loop>
20000030:   6805        ldr r5, [r0, #0]
20000032:   1b60        subs    r0, r4, r5
20000034:   bc30        pop {r4, r5}
20000036:   4770        bx  lr





55
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   220b        movs    r2, #11
2000001e:   2301        movs    r3, #1
20000020:   6804        ldr r4, [r0, #0]

20000022 <fact_loop>:
20000022:   3101        adds    r1, #1
20000024:   434b        muls    r3, r1
20000026:   4291        cmp r1, r2
20000028:   d4fb        bmi.n   20000022 <fact_loop>
2000002a:   6805        ldr r5, [r0, #0]
2000002c:   1b60        subs    r0, r4, r5
2000002e:   bc30        pop {r4, r5}
20000030:   4770        bx  lr
20000032:   bf00        nop




42
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   220b        movs    r2, #11
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   6804        ldr r4, [r0, #0]

20000024 <fact_loop>:
20000024:   3101        adds    r1, #1
20000026:   434b        muls    r3, r1
20000028:   4291        cmp r1, r2
2000002a:   d4fb        bmi.n   20000024 <fact_loop>
2000002c:   6805        ldr r5, [r0, #0]
2000002e:   1b60        subs    r0, r4, r5
20000030:   bc30        pop {r4, r5}
20000032:   4770        bx  lr


41
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   210b        movs    r1, #11
2000001c:   2301        movs    r3, #1
2000001e:   6804        ldr r4, [r0, #0]

20000020 <fact_loop>:
20000020:   434b        muls    r3, r1
20000022:   3901        subs    r1, #1
20000024:   d1fc        bne.n   20000020 <fact_loop>
20000026:   6805        ldr r5, [r0, #0]
20000028:   1b60        subs    r0, r4, r5
2000002a:   bc30        pop {r4, r5}
2000002c:   4770        bx  lr
2000002e:   bf00        nop



42
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   210b        movs    r1, #11
2000001c:   2301        movs    r3, #1
2000001e:   46c0        nop         ; (mov r8, r8)
20000020:   6804        ldr r4, [r0, #0]

20000022 <fact_loop>:
20000022:   434b        muls    r3, r1
20000024:   3901        subs    r1, #1
20000026:   d1fc        bne.n   20000022 <fact_loop>
20000028:   6805        ldr r5, [r0, #0]
2000002a:   1b60        subs    r0, r4, r5
2000002c:   bc30        pop {r4, r5}
2000002e:   4770        bx  lr



41
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   210b        movs    r1, #11
2000001c:   2301        movs    r3, #1
2000001e:   46c0        nop         ; (mov r8, r8)
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   6804        ldr r4, [r0, #0]

20000024 <fact_loop>:
20000024:   434b        muls    r3, r1
20000026:   3901        subs    r1, #1
20000028:   d1fc        bne.n   20000024 <fact_loop>
2000002a:   6805        ldr r5, [r0, #0]
2000002c:   1b60        subs    r0, r4, r5
2000002e:   bc30        pop {r4, r5}
20000030:   4770        bx  lr
20000032:   bf00        nop





FLASH ACR 0x30

2d

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr


2d

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr



 FLASH_ACR 0x00

2d

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


FLASH_ACR 0x02


5e
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

5f
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


FLASH_ACR 0x32

41


08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

 41

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


    PUT32(FLASH_ACR,0x3A);



41
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr
    ...

41
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr







flash acr 0x32


4c
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   46c0        nop         ; (mov r8, r8)
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fb        bne.n   8000028 <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr



4c

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   46c0        nop         ; (mov r8, r8)
 800002c:   434b        muls    r3, r1
 800002e:   3901        subs    r1, #1
 8000030:   d1fb        bne.n   800002a <fact_loop>
 8000032:   6805        ldr r5, [r0, #0]
 8000034:   1b60        subs    r0, r4, r5
 8000036:   bc30        pop {r4, r5}
 8000038:   4770        bx  lr


flash acr 0x30


38
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   46c0        nop         ; (mov r8, r8)
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fb        bne.n   8000028 <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


3b
0800002c <fact_loop>:
 800002c:   d002        beq.n   8000034 <fact_done>
 800002e:   434b        muls    r3, r1
 8000030:   3901        subs    r1, #1
 8000032:   e7fb        b.n 800002c <fact_loop>

08000034 <fact_done>:
 8000034:   6805        ldr r5, [r0, #0]
 8000036:   1b60        subs    r0, r4, r5
 8000038:   bc30        pop {r4, r5}
 800003a:   4770        bx  lr






38

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   2100        movs    r1, #0
 8000024:   220b        movs    r2, #11
 8000026:   2301        movs    r3, #1
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   3101        adds    r1, #1
 800002c:   434b        muls    r3, r1
 800002e:   4291        cmp r1, r2
 8000030:   d4fb        bmi.n   800002a <fact_loop>
 8000032:   6805        ldr r5, [r0, #0]
 8000034:   1b60        subs    r0, r4, r5
 8000036:   bc30        pop {r4, r5}
 8000038:   4770        bx  lr



38
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   2100        movs    r1, #0
 8000024:   220b        movs    r2, #11
 8000026:   2301        movs    r3, #1
 8000028:   46c0        nop         ; (mov r8, r8)
 800002a:   6804        ldr r4, [r0, #0]

0800002c <fact_loop>:
 800002c:   3101        adds    r1, #1
 800002e:   434b        muls    r3, r1
 8000030:   4291        cmp r1, r2
 8000032:   d4fb        bmi.n   800002c <fact_loop>
 8000034:   6805        ldr r5, [r0, #0]
 8000036:   1b60        subs    r0, r4, r5
 8000038:   bc30        pop {r4, r5}
 800003a:   4770        bx  lr





2d


08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

Пропустите здесь:

Обратите внимание, чтоЯ изменил количество циклов, входное значение с 3 на 11.

При нулевых состояниях ожидания на флэш-памяти и включенной предварительной выборке ваш цикл:

38
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   2100        movs    r1, #0
 8000024:   220b        movs    r2, #11
 8000026:   2301        movs    r3, #1
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   3101        adds    r1, #1
 800002c:   434b        muls    r3, r1
 800002e:   4291        cmp r1, r2
 8000030:   d4fb        bmi.n   800002a <fact_loop>
 8000032:   6805        ldr r5, [r0, #0]
 8000034:   1b60        subs    r0, r4, r5
 8000036:   bc30        pop {r4, r5}
 8000038:   4770        bx  lr

Это означает, что тактовая частота 0x38 междудве инструкции ldr.Выравнивание не повлияло на это в мгновение ока.

Если вы используете Питера или его вариант (bne имеет для меня больше смысла, чем плюс минус, YMMV) * ​​1042 *

2d
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

Выравнивание также не влияет на этот цикл,Это меньше инструкций, а также быстрее.

Таким образом, из другого ответа и документации mul и sub по одному такту каждая ветвь, когда берется, составляет 2 такта в соответствии с этим ответом, поэтому 4 такта в цикле 1144 часа или 0x2C.Нет сомнений, что эти два номера имеют стоимость, возможно, отсюда и дополнительные два часа.Или это может быть то, как работает модуль предварительной выборки или другое.

Ваш цикл равен 5 часам или 55 или 0x37, тот же ответ для дополнительных двух измеряемых часов.

Так что я слишком усложнил некоторые из нихэксперименты, блок предварительной выборки из ST и работа в нулевом состоянии ожидания позволили нам увидеть производительность, показанную в документации ARM.При обратном отсчете вместо повышения сохранялась инструкция в цикле, которая меньше по размеру и быстрее, чего вы и просили.

Ваши 5 часов за цикл 3 факторных значения означают 14 часов (5 + 5 + 4), ваши 22 часа (проверьте, как вы их измерили, очень часто у линейки есть проблема с тестированием, а не с кодом), где 8 часов где-то ещеминус 3 за инструкции по установке, если вы их считали.Какой бы линейкой вы ни пользовались, если используете решение обратного отсчета, посмотрите, как это сравнивается в вашей системе.Сохраняет пару инструкций, одну внутри и одну вне цикла.

------- EDIT

Я немного удивлен, что gcc не оптимизировал это в цикл обратного отсчета.Я пробовал только одну версию, возможно, более старая версия 3.x или 4.x.Также, если вы собираете для cortex-m3, он использует инструкцию thumb2, а не команду thumb.

unsigned int fact ( unsigned int x )
{
    unsigned int a;
    unsigned int rb;
    a=1;
    for(rb=1;rb<=x;rb++)
    {
        a*=rb;
    }
    return(a);
}
unsigned int fact2 ( unsigned int x )
{
    unsigned int a;
    a=1;
    while(x)
    {
        a*=x--;
    }
    return(a);
}

Да, я мог бы оптимизировать код C дальше ...

Disassembly of section .text:

00000000 <fact>:
   0:   b140        cbz r0, 14 <fact+0x14>
   2:   2301        movs    r3, #1
   4:   461a        mov r2, r3
   6:   fb03 f202   mul.w   r2, r3, r2
   a:   3301        adds    r3, #1
   c:   4298        cmp r0, r3
   e:   d2fa        bcs.n   6 <fact+0x6>
  10:   4610        mov r0, r2
  12:   4770        bx  lr
  14:   2201        movs    r2, #1
  16:   4610        mov r0, r2
  18:   4770        bx  lr
  1a:   bf00        nop

0000001c <fact2>:
  1c:   4603        mov r3, r0
  1e:   2001        movs    r0, #1
  20:   b123        cbz r3, 2c <fact2+0x10>
  22:   fb03 f000   mul.w   r0, r3, r0
  26:   3b01        subs    r3, #1
  28:   d1fb        bne.n   22 <fact2+0x6>
  2a:   4770        bx  lr
  2c:   4770        bx  lr
  2e:   bf00        nop

Iзабыл про cbz, я не использую thumb2 без необходимости, не такой универсально переносимый, как классические инструкции для большого пальца ...

более портативная версия:

Disassembly of section .text:

00000000 <fact>:
   0:   2800        cmp r0, #0
   2:   d007        beq.n   14 <fact+0x14>
   4:   2301        movs    r3, #1
   6:   2201        movs    r2, #1
   8:   435a        muls    r2, r3
   a:   3301        adds    r3, #1
   c:   4298        cmp r0, r3
   e:   d2fb        bcs.n   8 <fact+0x8>
  10:   0010        movs    r0, r2
  12:   4770        bx  lr
  14:   2201        movs    r2, #1
  16:   e7fb        b.n 10 <fact+0x10>

00000018 <fact2>:
  18:   0003        movs    r3, r0
  1a:   2001        movs    r0, #1
  1c:   2b00        cmp r3, #0
  1e:   d003        beq.n   28 <fact2+0x10>
  20:   4358        muls    r0, r3
  22:   3b01        subs    r3, #1
  24:   2b00        cmp r3, #0
  26:   d1fb        bne.n   20 <fact2+0x8>
  28:   4770        bx  lr
  2a:   46c0        nop         ; (mov r8, r8)

Хмммм:

  20:   4358        muls    r0, r3
  22:   3b01        subs    r3, #1
  24:   2b00        cmp r3, #0
  26:   d1fb        bne.n   20 <fact2+0x8>

вау.

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2 голосов
/ 10 апреля 2019

Комбинация r1 и r2 - очевидное решение, которое вы получаете также при мошенничестве с компилятором c ...

unsigned int foo(unsigned int a)
{
        unsigned int    res = 1;

        while (a > 0) {
                res *= a;
                --a;
        }

        return res;
}

переводится как

    subs    r3, r0, #0
    mov     r0, #1
    bxeq    lr
1:  mul     r0, r3, r0
    subs    r3, r3, #1
    bne     1b
    bx      lr
1 голос
/ 10 апреля 2019

Можно использовать что-то подобное: (предполагается, что 32-битные регистры, где 12! - максимально возможное значение), но Питер Кордес больше знаком с ARM (прошло 10 лет с тех пор, как я работал с ARM), и его основанным на кодеответ хороший.Таблица, которую я покажу ниже, должна быть самой быстрой, и она требует больше места, но не так много, поскольку диапазон равен 0!до 12!для 32-разрядных целых чисел без знака.

        mov     r2,#3      ;r2 = n
;       ...
        mov     r3,#1
        sub     r2,#2
        blo     factx
        mov     r1,#(fact11-fact12)
        mul     r1,r2,r1          ; or better, use a left-shift by 2 or 3 and an assemble time static assert that fact11-fact12 == 4 or 8
        adr     r2,fact2
        sub     r2,r2,r1
        mov     r1,#2
        b       r2            

fact12  mul     r3,r1,r3
        add     r1,r1,#1
fact11  mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
fact2   mul     r3,r1,r3
factx   ...                  ;r3 = n!

или, что еще проще, поиск в таблице:

tblfac  dcd     1,1,2,6,24,120,720,5040
        dcd     40320,362880,3628800,39916800
        dcd     479001600 
;       ...
        mov     r2,#3                    ;r2 = n

        adr     r3,tblfac
        ldr     r3,[r3, r2, lsl #2]      ;r3 = n!
...