Project

General

Profile

Программирование на ассемблере. Пара вопросов о производительности

Added by qewerty over 10 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 10 years ago

mouse, зачем использовать инкермент/декремент какой либо-переменной, чтобы отмерить промежуток времени. в посту выше[[http://multiclet.com/community/boards/4/topics/297?r=316#message-316]] был приведен пример использования таймера периферии для этой цели.
я вам ответил, потому что в вашем примере по факту регистр #33 не мог инкрементироваться, от этого вариант мог "не проходить".
спасибо за замечание, что состояние MODR и, скорее всего всех, индексных регистров при вызове из C для безопасности нужно сохранять.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 years ago

trott, про таймеры я тоже думал. Основная здесь проблема — использовать свободный таймер (хоть их и много у MCp). Попробую рассмотреть этот вариант ещё раз :) Спасибо.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

Как-то я писал функцию задержки, использовал системный таймер, при этом и возникали вопросы:

1) Использовать свободный таймер
2) Если нет свободного таймера, то сохранить период, предделитель таймера пользователя и конфигурацию, а затем добавить время задержки к расчёту.
Причём если время периода таймера с учётом предделителя меньше, чем задержка, то выполним задержку и только потом остановим таймер.

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

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

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

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 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 10 years ago

mouse wrote:

Есть ещё один вопрос. Правильно ли я понимаю, что будь сегодня у MCp не 4 клетки, а 64, то во время выполнения множества небольших параграфов (4-8 инструкций), почти 90% процессора будет простаивать? Основной значимый прирост будет только при массивных вычислениях в пределах одного параграфа?

Простаивать не будет:

1) Клетки можно будет разбить на группы и раздать по своим параграфам
2) И сейчас клетки бегут дальше по параграфам, если не было команд записи, а в дальнейшем возможен и вариант, когда клетки будут просто брать команды в произвольном порядке по мере освобождения
3) И в текущем процессоре имеется битик отключения контроля записи и чтения и тогда клетки будут бежать дальше не обращая внимания на запись, как сделано в примере с FFT.
4) Мультиклеточная архитектура допускает много вариантов по увеличению быстродействия

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 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.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

mouse wrote:

И ещё вопрос относительно оптимизации. Правильно ли я понимаю, что вместо:[...]оптимальнее писать:[...]По идее, в первом случае, вычисление каждой последующей инструкции зависит от результата предыдущей и должна быть какая-то синхронизация-ожидание, по сути, последовательное выполнение. Во втором случае всё параллелится и не зависит от предыдущего результата.

Я попытался соптимизировать кусок:[...]Приведя к виду:[...]В регистре имею значение 0x88, соответствующее адресу Sblock, но в модели читает значения 0x0000000f0000000f, 0x0000001600000010 и т.д, начиная с первого rdq #32.

Разумеется второй вариант будет оптимальнее. Просто не зачем сдвигать по 4, если можно сдвинуть на сколько вы хотите.
А можете привести полный вывод модели? Возможно это значения считанные из памяти? Ошибки в коде нет.

По остальным вопросам отвечу чуть позже. При использовании jg не забывайте, что все 64 бита РОНа должны быть равны нулю.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 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
хотя нигде ранее я этот регистр не использовал.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

Ну модель отработала верно, поскольку все записи проходят по complete. Именно поэтому в регистре #32 лежат нули.
Предупреждение ассемблера выглядит странно, ведь команда setl #32, @1 стоит в строке 45.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 years ago

Т.е. даже изменение регистра (по setl), а не только памяти, будет в конце параграфа? Были у меня такие смутные подозрения… :)

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

Да именно так. Записи в память и записи в регистры пройдут только по complete.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 years ago

Предупреждение ассемблера выглядит странно, ведь команда setl #32, @1 стоит в строке 45.

Это предупреждение из обновлённой версии. 44-ая строка — пустая, а на 46 setl.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

Приведите обновленную версию проверим откуда такое предупреждение.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 years ago

В данном примере у меня не получается выйти из gost.dec_prologue по условию:

je @idx, return

т.к. #IDX не хочет принимать нулевое значение в предыдущем заходе.
PS. параграфы надо было назвать gost_dec.epilogue / gost_enc.epilogue. Поспешил.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by qewerty over 10 years ago

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

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 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 10 years ago

Т.е. если написать цикл с декрементацией индексного регистра, то на последнем шаге вместо значения ноль модель опять присвоит 1? Проверим.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by mouse over 10 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 10 years ago

Кстати, если я после первого jl сделаю три "nop", потом jge и ещё три "nop", а затем последний je, поведение же изменится? Это, конечно, хак для 4-х клеточного…

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 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 10 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

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

Т.е. при "циклическом заходе" в функцию gost_encdec не сбрасывается в ноль 4-й РОН в модели. Завтра разберёмся с причиной. На плате как я понимаю всё отрабатывается верно.

RE: Программирование на ассемблере. Пара вопросов о производительности - Added by krufter_multiclet over 10 years ago

qewerty, можете выложить свою оптимизированную версию программы по перемножению матриц?

(26-50/82)