Ваш стиль программирования уже довольно хорош, и вы тщательно комментируете свой код. Это очень ценно, и вы должны продолжать делать это. Сам алгоритм кажется правильным и реализован приемлемым образом, хотя его можно было бы сделать более эффективно.
Я писал этот ответ, исходя из предположения, что вы программируете в режиме ARM. Однако большая часть советов применима и к режиму Thumb. Я полагаю, вы не можете использовать инструкции Thumb 2. c рекомендации для большого пальца отмечены наклонным шрифтом.
Самая важная вещь при написании эффективного ассемблерного кода - это знать набор команд архитектуры, для которой вы программируете. Ваш код написан для ARM, в котором есть много полезных инструкций и функций для ускорения работы. Начнем с некоторых основных c улучшений.
Прежде всего, вы используете эту последовательность, чтобы изолировать младший значащий бит R4
, чтобы затем проверить, не равен ли он нулю:
ADD R0, R0, #1 ; Calculates the bits
AND R3, R4, R1 ; Checks if R3 = R4
CMP R3, #1 ; Comparing result with 1
BEQ ONE ; Jump to ONE
Это можно сделать более эффективно. Во-первых, обратите внимание, что вы можете использовать немедленно с инструкцией AND
, поэтому нет необходимости хранить 1 в регистре только для этого:
AND R3, R4, #1
next, вместо сравнения результата побитового AND
с #1
, вы можете указать процессору, чтобы он установил флаги непосредственно из результата инструкции AND
. Это устанавливает нулевой флаг, если результат равен нулю (и, возможно, некоторые другие флаги, не слишком заботьтесь об этом), поэтому вы можете сразу перейти к результату.
ANDS R3, R4, #1 ; check if least significant bit set in R4
BNE ONE ; jump to ONE if it is
Теперь это ANDS
выполняет работу, но без необходимости записывает свой результат в R3
. Нам это там особо не нужно. Беглый взгляд на ссылку на набор инструкций говорит нам, что TST
делает то же самое, что и ANDS
, но отбрасывает результат, устанавливая только флаги. Это именно то, что мы хотим.
TST R4, #1 ; check if least signficant bit set in R4
BNE ONE ; jump to ONE if it is
Теперь следующее, что мы можем сделать, это избавиться от этой условной ветки. Единственная разница между кодом в ветке ONE
состоит в том, что он увеличивает R6
. Вместо условного перехода мы можем просто использовать функцию ARM условное выполнение для выполнения инструкции ADD
только при установленном нулевом флаге:
TST R4, #1 ; check if least significant bit set in R4
ADDNE R6, R6, #1 ; increment R6 if it is
Это делает код весьма интересным. немного эффективнее! Мы можем улучшить его еще больше, объединив TST
с инструкцией LSR
. Видите ли, если мы укажем LSR
установить флаги, он установит флаг переноса на последний бит, который был сдвинут. Это именно то, что нам интересно! Таким образом, мы можем просто сделать
LSRS R4, R4, #1 ; shift R4 to the right and set flags
ADDCS R6, R6, #1 ; increment R6 if a 1 was shifted out
Обратите внимание, что на других архитектурах, где условное выполнение недоступно, вы можете достичь эффекта, аналогичного ADDCS R6, R6, #1
, с помощью инструкции добавления с переносом:
ADC R6, R6, #0 ; add 1 to R6 if carry is set
Это то, что я бы сделал и в режиме большого пальца. Поскольку в режиме большого пальца нет немедленного операнда ADC
, вы должны сохранить один регистр равным нулю.
MOVS R1, #0
...
LSRS R4, R4, #1
ADCS R6, R1, #0 ; add carry to R6
Помимо установки флага переноса, LSRS
также устанавливает нулевой флаг если результат нулевой. Таким образом, мы можем избавиться от счетчика l oop, если просто будем выполнять итерацию до тех пор, пока все биты в R4
не будут сдвинуты, что сэкономит нам регистр и набор инструкций. Обратите внимание, что это может не дать правильных результатов, если какие-либо дополнительные биты (кроме как минимум 8 битов, которые вы проверяете) установлены в R4
, поэтому вы можете сначала замаскировать их с помощью AND R4, R4, #0xff
. А вот код:
LOOP: LSRS R4, R4, #1 ; shift R4 to the right and set flags
ADDCS R6, R6, #1 ; increment R6 if a 1 was shifted out
BNE LOOP ; loop until R4 is 0.
К сожалению, все инструкции большого пальца устанавливают флаги, поэтому вы не можете выполнить эту оптимизацию.
Аналогичным образом можно оптимизировать код в часть DONE
: по сути, вы просто проверяете, является ли R6
четным или нечетным, и возвращаете 1
, если оно нечетное, или 0
, если оно четное. Вы можете заменить весь каскад переходов одним тестом:
TST R6, #1 ; set the zero flag if R6 is even
BEQ RETURN0 ; return 0 if even
B RETURN1 ; otherwise return 1
Но тогда поймите, что это в основном то же самое, что и возврат младшего бита R6
, поэтому вы можете заменить весь этот код by
AND R0, R6, #1 ; set R0 to 1 if R6 is odd, 0 if R6 is even
POP {R4}
B STOP
Это немного короче, не так ли?
В коде большого пальца аналогичная производительность может быть достигнута с помощью некоторого умного мышления. Обратите внимание, что нас интересует только младший бит R6
, и удаление старших битов не имеет значения. Таким образом, мы можем написать
MOVS R0, #0 ; parity accumulator
SUBS R1, R0, #2 ; mask (clear in bit 0, 1 everywhere else)
LOOP: LSRS R4, R4, #1 ; shift out one bit from R4 and set flags
ADCS R0, R0, R1 ; add that bit to R0
CMP R4, #0 ; are we done?
BNE LOOP ; loop until we are
BICS R0, R1 ; isolate parity
Результат можно найти в R0
.
Теперь для некоторых улучшений алгоритмов c: ваш код делает свое дело, но действительно довольно медленно, так как выполняет одну итерацию на di git. Более быстрый подход - объединить биты вместе с помощью инструкций XOR
. Это позволяет нам вычислять четность всего за 3 шага вместо 8, как это делает ваш код:
LSR R3, R6, #4 ; keep a copy of R6 shifted by 4 places
EOR R6, R6, R3 ; and xor it into R6
LSR R3, R6, #2
EOR R6, R6, R3 ; same but shifted by 2 places
LSR R3, R6, #1
EOR R6, R6, R3 ; same but shifted by 1 place
AND R0, R6, #1 ; isolate parity
Тот же код может быть написан в режиме большого пальца, но вам могут потребоваться дополнительные перемещения данных в между.
Это можно улучшить, используя сдвинутые операнды, еще одну особенность ARM c:
EOR R6, R6, R6, LSR #4 ; xor R6 with R6 shifted right 4 places
EOR R6, R6, R6, LSR #2 ; xor R6 with R6 shifted right 2 places
EOR R6, R6, R6, LSR #1 ; xor R6 with R6 shifted right 1 place
AND R0, R6, #1 ; isolate parity
Это обычно самый быстрый подход без использования каких-либо расширений набора команд . Если у вас достаточно продвинутый процессор, вы можете использовать инструкцию CNT
для подсчета битов за один шаг, но в любом случае это не стоит усилий.