Forums » Программное обеспечение »
Программирование на ассемблере. Пара вопросов о производительности
Added by qewerty over 11 years ago
Вот допустим есть функция перемножения матриц 4 на 4 (указатели на стек и фрейм не меняются, т.к. локальные переменные на стеке не нужны). Матрицы хранятся по столбцам (как в OpenGL).
.global qf_mat44_mul qf_mat44_mul: .alias qf_mat44_mul.a #SP,4 .alias qf_mat44_mul.b #SP,8 .alias qf_mat44_mul.c #SP,12 jmp qf_mat44_mul.P0 ; сохранение адресов аргументов в регистрах rdl qf_mat44_mul.a setl #32, @1 rdl qf_mat44_mul.b setl #33, @1 rdl qf_mat44_mul.c setl #34, @1 complete .local qf_mat44_mul.P0 qf_mat44_mul.P0: rdl #SP jmp @1 ; чтение матрицы b va01 := rdq #32 va23 := rdq #32,8 va45 := rdq #32,16 va67 := rdq #32,24 va89 := rdq #32,32 vaAB := rdq #32,40 vaCD := rdq #32,48 vaEF := rdq #32,56 ; вспомогательные значения, для упаковки матрицы a в строки va54 := pack @va45, @va45 vaDC := pack @vaCD, @vaCD va76 := pack @va67, @va67 vaFE := pack @vaEF, @vaEF ; упаковка строк матрицы a va04 := patch @va45, @va01 va8C := patch @vaCD, @va89 va15 := pack @va54, @va01 va9D := pack @vaDC, @va89 va26 := patch @va67, @va23 vaAE := patch @vaEF, @vaAB va37 := pack @va76, @va23 vaBF := pack @vaFE, @vaAB ; чтение матрицы b vb01 := rdq #33 vb23 := rdq #33,8 vb45 := rdq #33,16 vb67 := rdq #33,24 vb89 := rdq #33,32 vbAB := rdq #33,40 vbCD := rdq #33,48 vbEF := rdq #33,56 ; скалярные произведения строк матрицы a со стобцами матрицы b madd @va04, @vb01 madd @va8C, @vb23 vc_0 := addf @1, @2 madd @va15, @vb01 madd @va9D, @vb23 vc_1 := addf @1, @2 madd @va26, @vb01 madd @vaAE, @vb23 vc_2 := addf @1, @2 madd @va37, @vb01 madd @vaBF, @vb23 vc_3 := addf @1, @2 ; -- madd @va04, @vb45 madd @va8C, @vb67 vc_4 := addf @1, @2 madd @va15, @vb45 madd @va9D, @vb67 vc_5 := addf @1, @2 madd @va26, @vb45 madd @vaAE, @vb67 vc_6 := addf @1, @2 madd @va37, @vb45 madd @vaBF, @vb67 vc_7 := addf @1, @2 ; -- madd @va04, @vb89 madd @va8C, @vbAB vc_8 := addf @1, @2 madd @va15, @vb89 madd @va9D, @vbAB vc_9 := addf @1, @2 madd @va26, @vb89 madd @vaAE, @vbAB vc_A := addf @1, @2 madd @va37, @vb89 madd @vaBF, @vbAB vc_B := addf @1, @2 ; -- madd @va04, @vbCD madd @va8C, @vbEF vc_C := addf @1, @2 madd @va15, @vbCD madd @va9D, @vbEF vc_D := addf @1, @2 madd @va26, @vbCD madd @vaAE, @vbEF vc_E := addf @1, @2 madd @va37, @vbCD madd @vaBF, @vbEF vc_F := addf @1, @2 ; упаковка результатов vc01 := patch @vc_1, @vc_0 vc23 := patch @vc_3, @vc_2 vc45 := patch @vc_5, @vc_4 vc67 := patch @vc_7, @vc_6 vc89 := patch @vc_9, @vc_8 vcAB := patch @vc_B, @vc_A vcCD := patch @vc_D, @vc_C vcEF := patch @vc_F, @vc_E ; запись результатов wrq @vc01, #34 wrq @vc23, #34,8 wrq @vc45, #34,16 wrq @vc67, #34,24 wrq @vc89, #34,32 wrq @vcAB, #34,40 wrq @vcCD, #34,48 wrq @vcEF, #34,56 complete
Есть кой-какие вопросы о том, как сделать её максимально быстрой:
1.) Имеет ли смысл сохранять адреса аргументов в индексных регистрах для дальнейшего использования?
Т.е. сделав:
rdl qf_mat44_mul.a setl #32, @1
Далее можно загрузить матрицу так:
va01 := rdq #32 va23 := rdq #32,8 va45 := rdq #32,16 va67 := rdq #32,24 va89 := rdq #32,32 vaAB := rdq #32,40 vaCD := rdq #32,48 vaEF := rdq #32,56
Вместо того, чтобы читать адрес в коммутатор и вычислять адреса вот так:
pa01 := rdl qf_mat44_mul.a pa23 := addl @pa01, 8 pa45 := addl @pa01, 16 pa67 := addl @pa01, 24 pa89 := addl @pa01, 32 paAB := addl @pa01, 40 paCD := addl @pa01, 48 paEF := addl @pa01, 56 va01 := rdq @pa01 va23 := rdq @pa23 va45 := rdq @pa45 va67 := rdq @pa67 va89 := rdq @pa89 vaAB := rdq @paAB vaCD := rdq @paCD vaEF := rdq @paEF
Но последнее позволяет реализовать функцию одним параграфом. Т.е. меня интересует есть ли смысл избегать переключения параграфов, а также скорость выполнения чтения из адреса в регистре со смещением и из вычисленного адреса в коммутаторе. Возможно есть какие-то подводные камни.
2.) Имеет ли смысл минимизировать кол-во операций записи в память?
Например, тут у меня сделано так:
; упаковка результатов vc01 := patch @vc_1, @vc_0 vc23 := patch @vc_3, @vc_2 vc45 := patch @vc_5, @vc_4 vc67 := patch @vc_7, @vc_6 vc89 := patch @vc_9, @vc_8 vcAB := patch @vc_B, @vc_A vcCD := patch @vc_D, @vc_C vcEF := patch @vc_F, @vc_E ; запись результатов wrq @vc01, #34 wrq @vc23, #34,8 wrq @vc45, #34,16 wrq @vc67, #34,24 wrq @vc89, #34,32 wrq @vcAB, #34,40 wrq @vcCD, #34,48 wrq @vcEF, #34,56
Но можно сделать и так:
wrl @vc_0, #34 wrl @vc_1, #34,4 wrl @vc_2, #34,8 ... wrl @vc_E, #34,56 wrl @vc_F, #34,60
Что делает 16 операций записи в память, а не 8, но все 16 операций независимы, тогда как в первом случае операции записи зависят от упаковки значений.
Replies (82)
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by trott over 11 years ago
mouse, зачем использовать инкермент/декремент какой либо-переменной, чтобы отмерить промежуток времени. в посту выше[[http://multiclet.com/community/boards/4/topics/297?r=316#message-316]] был приведен пример использования таймера периферии для этой цели.
я вам ответил, потому что в вашем примере по факту регистр #33 не мог инкрементироваться, от этого вариант мог "не проходить".
спасибо за замечание, что состояние MODR и, скорее всего всех, индексных регистров при вызове из C для безопасности нужно сохранять.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
trott, про таймеры я тоже думал. Основная здесь проблема — использовать свободный таймер (хоть их и много у MCp). Попробую рассмотреть этот вариант ещё раз :) Спасибо.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Как-то я писал функцию задержки, использовал системный таймер, при этом и возникали вопросы:
1) Использовать свободный таймер
2) Если нет свободного таймера, то сохранить период, предделитель таймера пользователя и конфигурацию, а затем добавить время задержки к расчёту.
Причём если время периода таймера с учётом предделителя меньше, чем задержка, то выполним задержку и только потом остановим таймер.
Ограничения ещё какие-то на пользователя можно наложить ну и сказать, что в случае использования задержки она может быть чуть больше чем нужно.
Функцию задержки я реализовал, надо только найти её среди программ. Но в ней насколько помню использовал прерывания по наименьшему отсчёту, т.е. смотря что меньше период пользователя или период задержки и подменял или корректнее сказать добавлял анализ задержки по таймеру.
В текущем процессоре индексные регистры пересчитываются на каждом параграфе, в новом процессоре можно будет указать конкретный параграф, в котором нужно пересчитывать индексный регистр. И надеюсь через версию сделают, что мне хочется - команду, в которой задаётся номер регистра 16 бит, шаг 16 бит, и по 16 бит на значение от и до скольки считать, чтобы можно было указывать конкретное значение и не рассчитывать маску индекс и базу.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Есть ещё один вопрос. Правильно ли я понимаю, что будь сегодня у MCp не 4 клетки, а 64, то во время выполнения множества небольших параграфов (4-8 инструкций), почти 90% процессора будет простаивать? Основной значимый прирост будет только при массивных вычислениях в пределах одного параграфа?
По поводу оптимизации самого компилятора. Будет ли возможность подсказывать компилятору, что определённые части кода можно или, наоборот, нельзя, смешивать в один параграф? Например:
PWM_InitStructure.Single_mode_en = 0; PWM_InitStructure.Change_period_en = 1; PWM_InitStructure.Select_mode = 00; PWM_InitStructure.Irq_full_en = 0; […]Можно объединить в один параграф:
main.P32: jmp main.P33 getl 0x0 getl 0x1 wrl @2, main.PWM_InitStructure.80AD; wrl @2, main.PWM_InitStructure.80AD + 4; wrl @4, main.PWM_InitStructure.80AD + 8; wrl @5, main.PWM_InitStructure.80AD + 12; […] completeЭкономим на getl, переходах и работе коммутатора. Запись в память между собой не пересекается, на периферию никак никак не влияет (переменная не volatile). Во! Пока писал, вспомнил про volatile, что его вполне достаточно для подсказки :) В случае же, если стуктура упакованная, то часть операций записи сменятся на логические операции вплоть до вычисления единого значения и меньшим числом операций записи.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
mouse wrote:
Есть ещё один вопрос. Правильно ли я понимаю, что будь сегодня у MCp не 4 клетки, а 64, то во время выполнения множества небольших параграфов (4-8 инструкций), почти 90% процессора будет простаивать? Основной значимый прирост будет только при массивных вычислениях в пределах одного параграфа?
Простаивать не будет:
1) Клетки можно будет разбить на группы и раздать по своим параграфам
2) И сейчас клетки бегут дальше по параграфам, если не было команд записи, а в дальнейшем возможен и вариант, когда клетки будут просто брать команды в произвольном порядке по мере освобождения
3) И в текущем процессоре имеется битик отключения контроля записи и чтения и тогда клетки будут бежать дальше не обращая внимания на запись, как сделано в примере с FFT.
4) Мультиклеточная архитектура допускает много вариантов по увеличению быстродействия
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Ого! Поиграюсь с найтройками. Спасибо.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Пытаюсь реализовать ГОСТовый алгоритм. В симуляторе возникла пара проблем. В аттаче код из поддиректории tst:
[…] subl @i, 32 jne @1, round jg @2, return completeКогда условие "jg" выполняется, моделирование прекращается вместо того, чтобы перейти в параграф return. Если изменить "subl @i, 33" и jg на jge, то всё нормально, но происходит вторая бяка. У меня в test.c два вызова gost_enc. При повторном вызове инструкция:
setq #IDX, 0 // в более свежей версии setl #3, 0хоть и отрабатывает, но в последствии инкремент идёт не относительно нуля, а относительно последнего значения #IDX после завершения первого вызова gost_enc.
Есть и третья проблема, в аттаче код из поддиректории tst2:
getl #IDX // .alias IDX 3 subl @1, 1 setl #IDX, @1Как только значение #3 достигает "1", а subl даёт нулевой результат, последующий setl перестаёт сохранять значение регистра и в следующем раунде #IDX опять "1".
И ещё вопрос относительно оптимизации. Правильно ли я понимаю, что вместо:
slrl @1, 4 slrl @1, 4 slrl @1, 4 // и так ещё пять разоптимальнее писать:
slrl @1, 4 slrl @2, 8 slrl @3, 12 // …По идее, в первом случае, вычисление каждой последующей инструкции зависит от результата предыдущей и должна быть какая-то синхронизация-ожидание, по сути, последовательное выполнение. Во втором случае всё параллелится и не зависит от предыдущего результата.
Я попытался соптимизировать кусок:
addl @s, 8 addl @s, 16 addl @s, 24 addl @s, 32 addl @s, 40 addl @s, 48 addl @s, 56 s0 := rdq @s s1 := rdq @8 s2 := rdq @8 s3 := rdq @8 s4 := rdq @8 […]Приведя к виду:
getl Sblock setl #32, @1 […] s0 := rdq #32 s1 := rdq #32, 8 s2 := rdq #32, 16 s3 := rdq #32, 24 s4 := rdq #32, 32 s5 := rdq #32, 40 s6 := rdq #32, 48 s7 := rdq #32, 56В регистре имею значение 0x88, соответствующее адресу Sblock, но в модели читает значения 0x0000000f0000000f, 0x0000001600000010 и т.д, начиная с первого
rdq #32
.test-gost.tar.gz (8.17 KB) test-gost.tar.gz |
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
mouse wrote:
И ещё вопрос относительно оптимизации. Правильно ли я понимаю, что вместо:[...]оптимальнее писать:[...]По идее, в первом случае, вычисление каждой последующей инструкции зависит от результата предыдущей и должна быть какая-то синхронизация-ожидание, по сути, последовательное выполнение. Во втором случае всё параллелится и не зависит от предыдущего результата.
Я попытался соптимизировать кусок:[...]Приведя к виду:[...]В регистре имею значение 0x88, соответствующее адресу Sblock, но в модели читает значения 0x0000000f0000000f, 0x0000001600000010 и т.д, начиная с первого
rdq #32
.
Разумеется второй вариант будет оптимальнее. Просто не зачем сдвигать по 4, если можно сдвинуть на сколько вы хотите.
А можете привести полный вывод модели? Возможно это значения считанные из памяти? Ошибки в коде нет.
По остальным вопросам отвечу чуть позже. При использовании jg не забывайте, что все 64 бита РОНа должны быть равны нулю.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
С rdq #20 всё намного интереснее. В аттаче два варианта. Первый работает, а второй выдаёт мусор. Разница только в месте, где я выставляю значение регистра #32. Такое ощущение, что изменение регистра ещё не успело произойти и чтение было, начиная с нулевого адреса.
Значение GP#3 было 0 (для всех 64 бит):
PM1: i=2f6 jg@2,0x8b; ir = 7ae800800000008b opcode=1e suf=2 type=e F1=2 F2=0 V=8b arg1=0 arg2=8b *sb=8b flag.f=1 1.000000000000008b <= 0000000000000000, 000000000000008b : jg @2, 0x8B --- complete --------------------- 00000000 : 0f 00 00 00 : ....
Я не очень понимаю, о чём это сообщение:
gost.s:46:10: warning: reinitialized register number '32'в строке:
setl #32, @1хотя нигде ранее я этот регистр не использовал.
test-gost-2.tar.gz (17.4 KB) test-gost-2.tar.gz |
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Т.е. даже изменение регистра (по setl), а не только памяти, будет в конце параграфа? Были у меня такие смутные подозрения… :)
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Да именно так. Записи в память и записи в регистры пройдут только по complete.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Предупреждение ассемблера выглядит странно, ведь команда setl #32, @1 стоит в строке 45.
Это предупреждение из обновлённой версии. 44-ая строка — пустая, а на 46 setl.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Приведите обновленную версию проверим откуда такое предупреждение.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Сейчас это уже 47-ая
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
В данном примере у меня не получается выйти из gost.dec_prologue по условию:
je @idx, return
т.к. #IDX не хочет принимать нулевое значение в предыдущем заходе.
PS. параграфы надо было назвать gost_dec.epilogue / gost_enc.epilogue. Поспешил.
test-gost-3.tar.gz (4.74 KB) test-gost-3.tar.gz |
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by qewerty over 11 years ago
mouse, да, кстати, я тоже натыкался на то, что модель не хочет устанавливать нулевое значение регистру, тогда как на плате выполняется как ожидается.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Что-то в присланном примере не увидел параграфа gost.dec_epilogue.
В этом параграфе у вас будет срабатывать jge, поскольку SF и OF флаги равны нулю.
И обратите внимание, что jl, jge анализируют #IDX - 1. Хотя #IDX должен стать равным нулю.
gost.dec_prologue: idx := getl #IDX subl @1, 1 setl #IDX, @1 subl @2, 25 jl @1, load_key1_24 jge @2, load_key25_32 je @idx, return complete
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Т.е. если написать цикл с декрементацией индексного регистра, то на последнем шаге вместо значения ноль модель опять присвоит 1? Проверим.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Что-то в присланном примере не увидел параграфа gost.dec_epilogue.
Да-да, я PS'ом написал, что параграфы обозвал иначе. Они там после параграфа gost_enc_dec.
Т.е. если написать цикл с декрементацией индексного регистра, то на последнем шаге вместо значения ноль модель опять присвоит 1?
У меня там не индексный регистр, а GP#4.
И обратите внимание, что jl, jge анализируют #IDX - 1. Хотя #IDX должен стать равным нулю.
Так я же указываю на результат от "subl @2, 25"? А je на результат @idx. И мне ещё нужно выполнить раунд с #IDX === 0.
Если я сделаю как-то так:
subl @2, 25 subl @3, 33 jl @2, load_key1_24 jl @2, load_key25_32 je @idx, returnБудет ли в диапазонах #IDX выполнятся:
1-24 переход на load_key1_24 и игнорирование результата "jl @2, load_key25_32" 25-32 переход на load_key25_32 33 переход на returnКаков вообще механизм обработки условий, если они пересекаются по результатам сравнения? Есть ли приоритеты у операторов сравнения?
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Кстати, если я после первого jl сделаю три "nop", потом jge и ещё три "nop", а затем последний je, поведение же изменится? Это, конечно, хак для 4-х клеточного…
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Для того, чтобы переходить на параграф load_key1_24 для 1-24 и на параграф load_key25_32 для 25-32 необходимо переписать параграф(аппаратно в первых двух версиях процессора логика на переходы одноимённые не предусмотрена):
pre: setl #0, 1 jmp test1 complete test1: getl #0 subl @1, 25 subl @2, 33 and @2, 0x80000000 jne @1, load_key1_24 and @3, 0x80000000 xor @1, @3 // 1 xor 1 = 0 jne @1, load_key25_32 or @5, @2 //1 or 0 = 1 je @1, return complete
Поведение процессора может быть непредсказуемым, если вы поставите несколько одноимённых переходов, условия которых будут выполнены одновременно, хак отменяется).
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 11 years ago
Спасибо! Условия отрабатывают.
Теперь осталась последняя проблема — повторные вызовы функции gost_encdec и не сбрасывающийся GPR#4 регистр:
PM0: i=f setl #4,0x0; ir = 1220010000000000 opcode=4 suf=2 type=2 F1=4 F2=0 V=0 arg1=4 arg2=0 *sb=76 flag.f=0 0.0000000000000076 <= 0000000000000004, 0000000000000000 : setl #4, 0x0 --- complete ---- PM1: i=10 getl #4; ir = d20100400000000 opcode=3 suf=1 type=2 F1=0 F2=4 V=0 arg1=4 arg2=0 *sb=0 flag.f=1 1.0000000000000000 <= 0000000000000004, 0000000000000000 : getl #4И второй вызов:
PM0: i=311 setl #4,0x0; ir = 1220010000000000 opcode=4 suf=2 type=2 F1=4 F2=0 V=0 arg1=4 arg2=0 *sb=76 flag.f=0 0.0000000000000076 <= 0000000000000004, 0000000000000000 : setl #4, 0x0 --- complete ---- PM1: i=312 getl #4; ir = d20100400000000 opcode=3 suf=1 type=2 F1=0 F2=4 V=0 arg1=4 arg2=21 *sb=21 flag.f=0 0.0000000000000021 <= 0000000000000004, 0000000000000021 : getl #4
test-gost-4.tar.gz (1.95 KB) test-gost-4.tar.gz |
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
Т.е. при "циклическом заходе" в функцию gost_encdec не сбрасывается в ноль 4-й РОН в модели. Завтра разберёмся с причиной. На плате как я понимаю всё отрабатывается верно.
RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 11 years ago
qewerty, можете выложить свою оптимизированную версию программы по перемножению матриц?