Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Ассемблерная оптимизация маленького куска (порядка 10-15 инструкций)
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
Himmler
Здравствуйте, товарищи.

Имеется старенький arm926, на нём потребовалось запилить определённый алгоритм.

Основная тяжеловесная часть данного алгоритма делают следующее:

На входе - 32-битное слово (лежит в регистре R0), в памяти (время доступа - 1 такт) лежат 4 таблицы по 256 однобайтовых элементов каждая.

-Выковыриваем из входного слова отдельно все 4 байта
-Из таблицы 1 дёргаем байт, позиция которого определяется первым выдернутым байтом входного слова
-Из таблицы 2 дёргаем байт, позиция которого определяется вторым выдернутым байтом входного слова
-Из таблицы 3 дёргаем байт, позиция которого определяется третьим выдернутым байтом входного слова
-Из таблицы 4 дёргаем байт, позиция которого определяется четвёртым выдернутым байтом входного слова
-Из четырёх надёрганных байтов формируем выходное 32-битное слово

Реализация в лоб занимает у меня 11 команд (4 логических И с маской, затем 4 загрузки из таблиц в регистры, затем 3 сложения, чтобы получить выходное слово. Сдвиги не учитываю, так как они прилеплены к другим командам и даются "бесплатно")

Есть ли предложения по сокращению количества команд, если алгоритмическая оптимизация недоступна (то есть только грамотным подбором ассемблерных инструкций) ?

Очень надеюсь, что есть опытные сограждане.
scifi
Может быть, можно сделать 4 байтовые загрузки в стек, и тогда слово там получится само без всяких сложений?
Пардон, ерунду сказал, в стек - это же отдельная инструкция.
jcxz
Цитата(Himmler @ Feb 26 2016, 20:46) *
На входе - 32-битное слово (лежит в регистре R0), в памяти (время доступа - 1 такт) лежат 4 таблицы по 256 однобайтовых элементов каждая.

-Выковыриваем из входного слова отдельно все 4 байта
-Из таблицы 1 дёргаем байт, позиция которого определяется первым выдернутым байтом входного слова
-Из таблицы 2 дёргаем байт, позиция которого определяется вторым выдернутым байтом входного слова
-Из таблицы 3 дёргаем байт, позиция которого определяется третьим выдернутым байтом входного слова
-Из таблицы 4 дёргаем байт, позиция которого определяется четвёртым выдернутым байтом входного слова
-Из четырёх надёрганных байтов формируем выходное 32-битное слово

Просто поразительно как Ваша задача напоминает решение задачи изложенной только что, практически одновременно с Вами в этом посте:
http://electronix.ru/forum/index.php?showt...p;#entry1407211
Правда там нужны таблицы не байтовые, а состоящий из 32-битных слов, и ядро другое - M0.
Но всё-же, всё-же..... очень подозрительно..... cool.gif

Касательно сути проблемы:
Вы не озвучили требования по памяти. Тупо в лоб оптимизация за счёт объёма памяти: разбивать входное 32-бит слово не на байты, а на куски большей разрядности - например 11/11/10 бит; ну или 16/16бит.
Памяти израсходуете больше, но тактов - меньше.

PS: Только что в голову пришёл самый быстрый метод - в зависимости от функционирования GPIO в Вашем МК, этот метод вообще может занимать всего пару тактов rolleyes.gif
Выделяете 2-а 32-битных порта GPIO. Соединяете вых. линии первого порта с вх. линиями второго в соответствии с таблицей перестановки бит, выводите слово в первый порт, считываете со второго. На ядре M3 этот алгоритм может занимать всего два такта, если GPIO сидит на быстрой шине.
ar__systems
Цитата(jcxz @ Feb 26 2016, 10:22) *
PS: Только что в голову пришёл самый быстрый метод - в зависимости от функционирования GPIO в Вашем МК, этот метод вообще может занимать всего пару тактов rolleyes.gif
Выделяете 2-а 32-битных порта GPIO. Соединяете вых. линии первого порта с вх. линиями второго в соответствии с таблицей перестановки бит, выводите слово в первый порт, считываете со второго. На ядре M3 этот алгоритм может занимать всего два такта, если GPIO сидит на быстрой шине.


Тут не перестановка битов, а выборка из массива.


Смотря что у вас там за доступы, может помочь кэширование результатов.
Himmler
Дело в том, что расширить таблицы мне уже не удастся, так как если сделать их не 1-байтовыми, а 2-байтовыми, то они будут занимать уже не 1 килобайт (4*16*16*1), а 256 килобайт (2*256*256*2).
А быстрой однотактовой памяти у меня всего 32 килобайта. Есть огромная ддр-ка, но она дико медленная, обращение к ней съест весь выигрыш. И прекешировать нужное не получится, потому как индексы прыгают хаотически согласно входному слову.

Насчёт GPIO идеи не понял - как можно соединить входы с выходами так, чтобы такое соединение охватывало 2^32 состояний ?

Я пока копаю в сторону именно удачно подходящих инструкций, например чтобы не выделять отдельно 4 байта четыремя командами, или например избавиться от тройного сложения и как-то иначе собрать выходное 32-битное слово из однобайтовых кусков.
jcxz
Цитата(Himmler @ Feb 26 2016, 22:58) *
Дело в том, что расширить таблицы мне уже не удастся, так как если сделать их не 1-байтовыми, а 2-байтовыми, то они будут занимать уже не 1 килобайт (4*16*16*1), а 256 килобайт (2*256*256*2).
А быстрой однотактовой памяти у меня всего 32 килобайта. Есть огромная ддр-ка, но она дико медленная, обращение к ней съест весь выигрыш. И прекешировать нужное не получится, потому как индексы прыгают хаотически согласно входному слову.

Но 11/11/10 потребует только (2^11*2+2^10)*2 = 10240 байт. Хотя (не помню точно систему команд) возможно потребуется доп. сдвиг для преобразования индекса в смещение.

Цитата(Himmler @ Feb 26 2016, 22:58) *
Насчёт GPIO идеи не понял - как можно соединить входы с выходами так, чтобы такое соединение охватывало 2^32 состояний ?

Забудьте. Я думал у Вас такая-же задача, как у товарища переставляющего биты.
Himmler
В принципе да, я могу уместиться в память, если переделаю 4 таблицы по 8 бит в 3 таблицы по 12/12/8 бит.

Тогда алгоритм получится следующий:

-Выковыриваем из входного слова отдельно 12 байт, потом ещё 12, и последние 8.
-Из таблицы 1 дёргаем 12 бит, позиция которых определяется первым выдернутым значением из входного слова
-Из таблицы 2 дёргаем 12 бит, позиция которых определяется вторым выдернутым значением из входного слова
-Из таблицы 3 дёргаем 8 бит, позиция которого определяется третьим выдернутым значением из входного слова
-Из трёх надёрганных кусков формируем выходное 32-битное слово

Итого: 3 логических И с маской, 3 загрузки из памяти в регистры, 2 суммирования.
Экономия, несомненно есть.

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

P.S. Вот я тут подумал, при разбиении входного слова на куски можно ещё одну инструкцию сэкономить - не брать маску для старших бит, а передать входное слово с нужным сдвигом, получив тем самым нужное количество старших бит в качестве индекса внутри массива в третьей таблице.
controller_m30
Можно в 9 команд уложиться.
В R0 исходное слово 32 бит
В R1 адрес первой таблицы(table_0), в процессе выполнения не меняется,
Таблицы идут последовательно: table_0, table_1, table_2, table_3
На выходе в R6 - значения таблиц по условию: [t3],[t2],[t1],[t0].
adnega
Цитата(controller_m30 @ Feb 26 2016, 23:09) *
Можно в 9 команд уложиться.

Только борьба не за память идет, а за быстродействие.
Разверните цикл и посмотрите сколько инструкций получится.
Ga_ry
Если организовать результирующее слово как структуру из указателей?
Himmler
Действительно, я наверное некорректно в постановке вопроса употребил слово "команда". В моём случае все инструкции, кроме загрузки из памяти выполняются за 1 такт, а латентность, вызываемая загрузкой из памяти маскируется во-первых конвейерной загрузкой, а во-вторых использованием загруженных данных через 2 инструкции после команды загрузки.
А борьба идёт, естественно, за количество тактов. И предложенное выше укрупнение таблиц + экономия одной операции с маской позволяет выполнить алгоритм за 7 тактов. На мой взгляд, гипотетически сэкономить можно на сложении трёх значений и на операциях с маской. Но как именно - пока не знаю.

Цитата(Ga_ry @ Feb 27 2016, 00:24) *
Если организовать результирующее слово как структуру из указателей?

А именно ?
controller_m30
Цитата(Himmler @ Feb 26 2016, 23:29) *
А борьба идёт, естественно, за количество тактов.

Понятно.
А как оказались четыре 8-битных числа собранными в один регистр R0? Зачем их туда сложили?
Если эти 4 байта лежали рядом в какой-то другой таблице, то их можно было считать побайтно в 4 отдельных регистра и не пользоваться для их разделения масками и сдвигами вообще.

Можно ещё сэкономить время на сдвигах (безотносительно к вышенаписанному), если сделать 4 таблицы (каждая по 256*32битных слова) с заранее смещёнными в них 8-битными данными.
В таблице 0 все данные вида 0х000000NN, в таблице 1 данные вида 0х0000NN00, и так далее.
Тогда таблицы займут 4кБ всего. А данные берутся из памяти за 1 такт, и ещё за 1 складываются в общий регистр без сдвига (который тоже вроде бы ест такты).
Вынуть все 4 байта из 4 таблиц, и сложить в один регистр займёт 7 тактов:
Himmler
Цитата(controller_m30 @ Feb 27 2016, 01:15) *
Понятно.
А как оказались четыре 8-битных числа собранными в один регистр R0? Зачем их туда сложили?
Если эти 4 байта лежали рядом в какой-то другой таблице, то их можно было считать побайтно в 4 отдельных регистра и не пользоваться для их разделения масками и сдвигами вообще.

Можно ещё сэкономить время на сдвигах (безотносительно к вышенаписанному), если сделать 4 таблицы (каждая по 256*32битных слова) с заранее смещёнными в них 8-битными данными.
В таблице 0 все данные вида 0х000000NN, в таблице 1 данные вида 0х0000NN00, и так далее.
Тогда таблицы займут 4кБ всего. А данные берутся из памяти за 1 такт, и ещё за 1 складываются в общий регистр без сдвига (который тоже вроде бы ест такты).
Вынуть все 4 байта из 4 таблиц, и сложить в один регистр займёт 7 тактов:


Это не 4 8-битных числа, разбиение условное, можно побить как 12/12/8. Входной вектор - 32-битный, выходной - тоже 32-битный. Идеальный вариант однотактового преобразователя - таблица на 4 миллиарда состояний, по 32 бита в каждом. Но ввиду очевидной невозможности такого безумия приходится разбивать преобразование на куски. Можно преобразовывать по 4 бита 8 раз, а можно по 8 бит 4 раза, а можно по 16 бит 2 раза (но на это памяти уже не хватит). По доступной памяти уложусь максимум в 12/12/8.
AVI-crak
Не понимаю как у вас получается 9 команд, и 18 в уме. У меня получилось 20 команд, и 35 системных тиков.
Код
        // Cortex-M3
        // r0  вход-выход    
        push   {r1, r2, r3, r4, r5, lr}
        ldr     r0, =входное
        ldr     r1, =адрес таблиц (1+2+3+4)
        mov     r2, #0xFF                   // маска
        and     r3, r0, r2
        ldrb    r4, [r1, r3]                // первый байт
        and     r3, r2, r0, lsr #8
        add     r0, r0, #0x100
        ldrb    r5, [r0, r3]                // второй байт
        add     r0, r0, #0x100
        and     r3, r2, r0, lsr #16
        add     r4, r4, r5, lsl #8
        ldrb    r5, [r0, r3]                // третий байт
        add     r0, r0, #0x100
        lsr     r0, r0, #24
        add     r4, r4, r5, lsl #16
        ldrb    r5, [r0, r3]                // последний байт
        add     r0, r4, r5, lsl #24
        pop     {r1, r2, r3, r4, r5, lr}

Можно ещё читать байт прямо в разряды регистра, но без выравнивания таблиц - может получиться кака.
Himmler
Цитата(AVI-crak @ Feb 27 2016, 03:36) *
.


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

Примерно получается (если считать, что входное слово, 12-битная маска, адреса таблиц уже загружены,регистры сохранены, и таблиц всего 3 по 12/12/8 бит):

Код
and r1, r0, r7; получаем указатель в первой таблице (r7 = 0x00000fff)
and r2, r0, r7, LSL #12; получаем указатель во второй таблице

ldr r1, [r4, r1]; загружаем младшие 12 бит (r4 был предварительно загружен и будет ещё многократно использоваться)
ldr r2, [r5, r2, LSR #12]; загружаем средние 12 бит (r5 был предварительно загружен и будет ещё многократно использоваться)
ldr r3, [r6, r0, LSR #24]; загружаем старшие 8 бит (r6 был предварительно загружен и будет ещё многократно использоваться)

add r1, r1, r2, LSL #12; собираем 3 куска в одно 32-битное слово
add r1, r3, LSL #24; собрали
jcxz
Цитата(Himmler @ Feb 27 2016, 13:41) *
Примерно получается (если считать, что входное слово, 12-битная маска, адреса таблиц уже загружены,регистры сохранены, и таблиц всего 3 по 12/12/8 бит):

Не понимаю - почему Вы выбрали 12/12/8, а не 11/11/10? Ведь первый вариант требует 16896 байт таблиц, в то время как 2-ой - только 10240. wacko.gif

Цитата(Himmler @ Feb 27 2016, 13:41) *
ldr r1, [r4, r1]; загружаем младшие 12 бит (r4 был предварительно загружен и будет ещё многократно использоваться)
ldr r2, [r5, r2, LSR #12]; загружаем средние 12 бит (r5 был предварительно загружен и будет ещё многократно использоваться)
ldr r3, [r6, r0, LSR #24]; загружаем старшие 8 бит (r6 был предварительно загружен и будет ещё многократно использоваться)

Забываете, что читаете не байты, а слова. Надо сдвинуть все индексы влево на 1 бит. Так что кол-во команд будет на 1 больше. И команды не LDR, а LDRH.
Himmler
В моём случае не принципиально 10 килобайт или 16, а дискреты в 4 бита мне удобнее для пересчёта таблиц из их первоначального вида (где было 8 таблиц по 16 4-битных значений в каждой)

Насчёт не байты, а слова - да, лопухнулся, написал как было раньше, когда было 4 однобайтовых таблицы. Сдвигать нужно на 1 бит индексы у двух таблиц, третья осталась однобайтовой.
Только вот не понял, откуда ещё 1 команда, вроде же достаточно поменять на код вида
Код
ldr r1, [r4, r1, LSL#1]; загружаем младшие 12 бит (r4 был предварительно загружен и будет ещё многократно использоваться)
ldr r2, [r5, r2, LSR #11]; загружаем средние 12 бит (r5 был предварительно загружен и будет ещё многократно использоваться)
ldr r3, [r6, r0, LSR #24]; загружаем старшие 8 бит (r6 был предварительно загружен и будет ещё многократно использоваться)
jcxz
Вы упорно пишете LDR, хотя Вам надо грузить 16-битные слова twak.gif
Доп. команда добавляется если использовать 11/11/10 рассечение (нужно маскировать и сдвинуть старшие 10бит, а это не получится сделать сразу в LDRH). Ок, тогда оставим 12/12/8.
Тогда получим:
Код
;исх. данные:
;R0 - аргумент;
;R7 = 0x1FFE
AND R1, R7, R0, LSL #1;получаем индекс*2 в первой таблице
AND R2, R7, R0, LSR #12-1;получаем индекс*2 во второй таблице
LDRH R1, [R4, R1];загружаем младшие 12 бит (r4 был предварительно загружен и будет ещё многократно использоваться)
LDRH R2, [R5, R2];загружаем средние 12 бит (r5 был предварительно загружен и будет ещё многократно использоваться)
LDRB R3, [R6, R0, LSR #24];загружаем старшие 8 бит (r6 был предварительно загружен и будет ещё многократно использоваться)
ADD R1, R1, R2, LSL #12;собираем 3 куска в одно 32-битное слово
ADD R1, R3, LSL #24
Himmler
Опять же согласен, вгружать 32-битные слова неправильно.

То есть на данный момент 7 тактов. Больше нигде и ничего не срезать ?
AVI-crak
LDRB R3, [R6, R0, LSR #24] - это наверное из другой вселенной.
В скобках адрес, при этом модификации подвергается последний регистор в записи,
и двигать (умножать) его можно исключительно в лево,
и число для модификации от единицы до трёх,
и чтение байта с такой модификацией - теряет смысл, ибо глюканёт на несоответствии объявленных границ выравнивания переменных и зарегистрированной командой.

В целом трешь и угар, выдыхайтеsm.gif.
Himmler
К сожалению прямо сейчас у меня железяки и полноценного ide под рукой нету, смогу только завтра проверить.

Но ARMSim#.191, onlinedisassembler.com и ARM instruction evaluator прекрасно переварили такой код, взаимно понимая друг друга и отлично ассемблируя/дизассемблируя результаты друг друга.
В частности LDRB R3, [R6, R0, LSR #24] (машинный код E7D63C20) делает именно то, что задумывалось.

Himmler
Извиняюсь за задержку, сегодня проверил код, погонял на разных входных данных - работает именно так, как нужно
Код
;R0 - используется другим куском кода
;R1 - используется другим куском кода
;R2 - здесь храним кусок (12 бит)
:R3 - здесь храним второй кусок (12 бит)
;R4 = in_data
;R5  = table_0_pointer = 0x4000
;R6  = table_1_pointer = 0x6000
;R7  = table_2_pointer = 0x3F00
;R8 = mask (0x1FFE)
;R9-R14 - используется другим куском кода


AND R2, R8, R4, LSL #1
AND R3, R8, R4, LSR #11
LDRH R2, [R5, R2]
LDRH R3, [R6, R3]
LDRB R4, [R7, R4, LSR #24]
ADD R2, R2, R3, LSL #12
ADD R2, R2, R4, LSL #24


Поглядел на весь код в целом - было бы очень хорошо сэкономить два регистра. Ну или хотя бы один.
Предположительно, это можно как-нибудь сделать, не храня все три адреса в R5, R6 и R7, а храня один их них, а два других -вычисляя на лету. Но тут я столкнулся с тем, что LDRH не хочет делать сдвиги и суммирования, как это делают LDR и LDRB.
Можно ли тут как-нибудь извернуться ? Разумеется, не увеличивая число инструкций.
Himmler
Сегодня начал мерить производительность кода и познакомился с кучей неприятных сюрпризов архитектуры ARM.

Во-первых блокировка при загрузке байт/слов составляет не один такт, а два.
Во-вторых все сдвиги, прилепленные к командам, совсем не бесплатные, и тоже добавляют блокировку на использование сдвигаемого регистра на один такт.

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

Единственное, что сразу пришло на ум и чуть-чуть ускорило выполнение - перенос одной команды загрузки слова на пораньше. Но особой погоды это не сделало.

Поэтому актуален следующий вопрос:

Как можно изменить код (в том числе с добавлением команд и расходованием доп. регистров), чтобы уменьшить именно количество тактов при выполнении ?
Obam
Цитата(Himmler @ Mar 3 2016, 20:38) *
Сегодня начал мерить производительность кода и познакомился с кучей неприятных сюрпризов архитектуры ARM.


Это не сюрпризы, всё документировано (DDI0439B, DDI0210B). Частично этим и объясняется, что x86 уделывала ARM когда-то.
AlexRayne
Цитата(Himmler @ Mar 3 2016, 19:38) *
Сегодня начал мерить производительность кода и познакомился с кучей неприятных сюрпризов архитектуры ARM.

Во-первых блокировка при загрузке байт/слов составляет не один такт, а два.
Во-вторых все сдвиги, прилепленные к командам, совсем не бесплатные, и тоже добавляют блокировку на использование сдвигаемого регистра на один такт.

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

Единственное, что сразу пришло на ум и чуть-чуть ускорило выполнение - перенос одной команды загрузки слова на пораньше. Но особой погоды это не сделало.

Поэтому актуален следующий вопрос:

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

а развернуть цикл, и конвертировать паралельно 2 числа, может както уменьшить простои?
AVI-crak
Цитата(Himmler @ Mar 3 2016, 23:38) *
Сегодня начал мерить производительность кода и познакомился с кучей неприятных сюрпризов архитектуры ARM.


Это наверное ваша команда чтения из памяти виновата LDRB R3, [R6, R0, LSR #24]
У старенького arm926 - ядро ARM7TDMI, и его команды имеют чёткое описание
http://www.gaw.ru/html.cgi/txt/doc/micros/...tmi/insruct.htm

Формат чтения памяти : [Rn, +/-Rm, LSR #5bit_shift_imm] , но в реальности LSR-LSL - это пятый бит смещения, а нулевой бит просто не участвует в операции - и того 3 бита смещения. То-есть как у всех ARM чипов.

scifi
Цитата(AVI-crak @ Mar 4 2016, 17:41) *
У старенького arm926 - ядро ARM7TDMI

Это что-то новенькое cranky.gif

Цитата(AVI-crak @ Mar 4 2016, 17:41) *
и его команды имеют чёткое описание

Да. См. тут.
AVI-crak
Цитата(scifi @ Mar 4 2016, 21:51) *
Это что-то новенькое cranky.gif


Да. См. тут.


А тут значится ложная инфа ? https://www.arm.com/products/processors/cla...arm9/arm926.php

И тут. https://www.arm.com/products/processors/cla...arm9/arm926.php, заявлена двоичная совместимость с ядром ARM7TDMI.
scifi
Цитата(AVI-crak @ Mar 4 2016, 18:14) *
А тут значится ложная инфа ? https://www.arm.com/products/processors/cla...arm9/arm926.php

И тут. https://www.arm.com/products/processors/cla...arm9/arm926.php, заявлена двоичная совместимость с ядром ARM7TDMI.

Нет там никакой ложной информации. ARM926 и ARM7TDMI - это разные процессоры. Двоичная совместимость не предполагает одинаковые времена выполнения инструкций (хотя случайно может и совпадать местами). Точно так же, как Cortex-M4 двоично совместим с Cortex-M3, но время выполнения некоторых инструкций отличается.
_pv
Цитата(AVI-crak @ Mar 4 2016, 21:14) *
А тут значится ложная инфа ? https://www.arm.com/products/processors/cla...arm9/arm926.php
И тут. https://www.arm.com/products/processors/cla...arm9/arm926.php, заявлена двоичная совместимость с ядром ARM7TDMI.

двоичная совместимость это несколько не то.
компьютер что у вас на столе сейчас стоит тогда тоже можно 80386 обозвать, но от процессора тридцатилетней давности он всё-таки несколько отличается.
соответственно и у ARMv5 по сравнению с ARMv4 отличия тоже есть.
Himmler
Дело в том, что у меня и так линейный код, без ветвлений, циклов и прочего. Только арифметические операции и загрузка из памяти. Параллелить его не получится, поскольку он идеологически последовательный. Весь параллелизм - это сложить что-нибудь, пока что-то другое загружается из памяти.
Просто судя по времени выполнения, в приведённом мной коде в среднем к каждой команде добавляется ещё и штрафной NOP. Откуда берутся такие задержки - пока не знаю.
jcxz
Цитата(Himmler @ Mar 3 2016, 00:16) *
Поглядел на весь код в целом - было бы очень хорошо сэкономить два регистра. Ну или хотя бы один.
Предположительно, это можно как-нибудь сделать, не храня все три адреса в R5, R6 и R7, а храня один их них, а два других -вычисляя на лету. Но тут я столкнулся с тем, что LDRH не хочет делать сдвиги и суммирования, как это делают LDR и LDRB.

Но, судя по системе команд, команда LDRH регистр смещения может как прибавлять к базовому регистру, так и вычитать из базового (если я правильно понимаю описание - не уверен, надо проверить на железе, но не на чем в данный момент).
Так что если в коде
Код
AND R2, R8, R4, LSL #1
AND R3, R8, R4, LSR #11
LDRH R2, [R5, R2]
LDRH R3, [R6, R3]
LDRB R4, [R7, R4, LSR #24]
ADD R2, R2, R3, LSL #12
ADD R2, R2, R4, LSL #24
нулевой индекс таблиц R5 и R6 содержит одинаковое значение, то можно их расположить одна за другой (объединив 0-й индекс) в командах LDRH использовать один базовый регистр, установленный на середину этой объединённой таблицы, и в одной LDRH использовать смещение в '+', а в другой - в '-'. Тогда один регистр сэкономится.

Цитата(Himmler @ Mar 5 2016, 00:06) *
Дело в том, что у меня и так линейный код, без ветвлений, циклов и прочего. Только арифметические операции и загрузка из памяти. Параллелить его не получится, поскольку он идеологически последовательный. Весь параллелизм - это сложить что-нибудь, пока что-то другое загружается из памяти.
Просто судя по времени выполнения, в приведённом мной коде в среднем к каждой команде добавляется ещё и штрафной NOP. Откуда берутся такие задержки - пока не знаю.

Я думаю предлагавший имел в виду разбавить данный кусок кода командами из смежного кода, расположив их в местах предполагаемых "NOP"-ов.
Вам вначале нужно выяснить где именно появляются штрафные такты (эти самые NOPы), чтобы можно было с ними бороться перестановкой и перераспределением инструкций.
Если штрафы накладываются слишком близким расположением точки вычисления адреса/смещения к команде в которой этот адрес используется
(это пары AND R2, R8, R4, LSL #1 / LDRH R3, [R6, R3] и AND R3, R8, R4, LSR #11 / LDRH R3, [R6, R3]) и если штраф - всего один такт, то можно команду LDRB R4, [R7, R4, LSR #24]
сместить на две строчки выше - это уберёт штрафы. Если штрафы больше 1-го такта, то надо этот кусок разбавлять командами из смежного кода.
Himmler
Ну про перенос команды загрузки я ранее уже писал, это маленько прибавило скорости. А вот про хранение одного адреса на 2 таблицы - не удалось, потому как размер таблиц - 0x2000, а такое значение не влезет в код команды. Изначально у меня была идея разместить таблицы так:
0x3F00 (маленькая таблица на 256 байт)
0x4000
0x6000

При этом хранить только 0x4000, а из него получать сдвигом 0x6000 и вычитанием 0x3F00. Вот только LDRH не позволяет сдвинуть, в отличие от LDR.
jcxz
Вы не поняли.... sad.gif
Вот описание команды LDRH ядра ARM7TDMI: http://electronix.ru/redirect.php?http://w...arm/lds_str.htm
перемотайте до Рис.25
Нас интересует смещение не в виде константы (рис.26), а в виде регистра (рис.25).
Обратите внимание на бит U.
Так вот: если таблицу, адрес которой находится в R6 записать в обратном порядке и совместить нулевые индексы таблиц R5 и R6 в одном слове, то должно помочь.
Т.е. - при размере таблиц == N элементов будет:
(N-1)-ый индекс таблицы R6
(N-2)-ый индекс таблицы R6
...
(1)-ый индекс таблицы R6
(0)-ой индекс таблиц R5 и R6 (единый)
(1)-ый индекс таблицы R5
...
(N-2)-ый индекс таблицы R5
(N-1)-ый индекс таблицы R5
устанавливаете регистр R5 на адрес (0)-ой индекс таблиц R5 и R6 (единый) и адресуете обе части таблицы командами:
LDRH R2, [R5, R2]
LDRH R3, [R5, -R3]

Освобождается регистр R6.
Himmler
Точно, неплохая идея. Даже один сэкономленный регистр будет уже в плюс.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.