Как глобализация 2 небольших массивов может привести к значительному снижению производительности?
У меня есть 2 небольших локальных массива:
short int xpLeft [4], xpRight [4];
В тот момент, когда я делаю их глобальными (для доступа к ним в другом методе, но только в одном файле C (например, недоступном для других модулей)), производительность (на Motorola 68000) падает. Вместо 224 вбланков (для локальных), весь бенчмарк (рендерит 320 кадров сцены) вдруг занимает 249 вбланков (глобальный массив)!
Что я пробовал:
Поскольку данные в массивах не использовались в этой функции, я решил, что компилятор поймал, что и не удосужился записать полученное значение (из регистра) в память (крайне медленная операция на 68000 - обращение к памяти). Поэтому я добавил небольшой код для использования этих значений массива в конце функции, и это соответственно повысило стоимость производительности (всего 1 vblank).
Что могло бы помочь:
Мне нужно изучить окончательный код ASM (и сравнить обе версии), но я не знаю, как это сделать с помощью компилятора vbcc (от Dr.Volker). Я попробовал несколько переключатели из документации, и хотя они производили некоторый промежуточный вывод, я не мог заставить его предоставить полный список ASM каждого модуля (с именами функций из C).
Что Я думаю, что происходит:
- создание массивов глобальными помещает их в разные адреса в оперативной памяти, и контроллер памяти должен получить доступ к другому банку, и банк переключатель-это чрезвычайно медленная операция на целевой платформе. - приводя к циклам заряда RAS (для доступа к различным рядам адреса).
- сглаживание указателя-возможно, компилятор генерирует другой код и может фактически обращаться к фактической памяти для промежуточного результаты - но если бы у меня был выход ASM для каждая функция, я мог бы понять это легко
Какие-либо советы о том, почему это происходит или как получить полный список выходных данных vbcc каждого скомпилированного модуля с соответствующим кодом ASM ?
С помощью вывода ASM я создал небольшой тестовый репро-кейс:
short int tmpfn1 ()
{
short int xpLeft [4], xpRight [4];
short int i, tmp;
for (i = 0; i < 4; i++)
{
xpLeft [i] = 137 + i;
xpRight [i] = 215 + i;
}
tmp = xpLeft [0] + xpRight [0];
return tmp;
}
Вот результат ASM. Хотя ASM довольно понятен, я все равно добавил несколько комментариев:
public _tmpfn1
cnop 0,4
_tmpfn1
sub.w #16,a7
movem.l l4150,-(a7)
moveq #0,d1
lea (0+l4152,a7),a1 ; a1 = &xpLeft [0]
lea (8+l4152,a7),a2 ; a2 = &xpRight [0]
move.w #215,d3 ; d2/d3 = The Bulgarian constants
move.w #137,d2
l4148
move.w d1,d0
ext.l d0
lsl.l #1,d0
move.w d2,(0,a1,d0.l) ; xpLeft [i] = 137 + i;
move.w d3,(0,a2,d0.l) ; xpRight [i] = 215 + i;
addq.w #1,d1 ; d1 = Loop Counter (i++)
addq.w #1,d2
addq.w #1,d3
cmp.w #4,d1
blt l4148 ; Repeat the loop
move.w (8+l4152,a7),d0
add.w (0+l4152,a7),d0 ; tmp = xpLeft [0] + xpRight [0];
l4150 reg a2/d2/d3
movem.l (a7)+,a2/d2/d3
add.w #16,a7
l4152 equ 12
rts
; stacksize=28
opt 0
opt NQLPSMRBT
Теперь я перейду к размещению массивов из локального в глобальный.
Вот код с глобальная переменная.
public _tmpfn1
cnop 0,4
_tmpfn1
movem.l l4150,-(a7)
moveq #0,d1
move.w #215,d2
move.w #137,d3
l4148
move.w d1,d0
ext.l d0
lsl.l #1,d0
lea _AxpLeft,a0
move.w d3,(0,a0,d0.l)
lea _AxpRight,a0
move.w d2,(0,a0,d0.l)
addq.w #1,d1
addq.w #1,d3
addq.w #1,d2
cmp.w #4,d1
blt l4148
move.w _AxpRight,d0
add.w _AxpLeft,d0
l4150 reg d2/d3
movem.l (a7)+,d2/d3
l4152 equ 8
rts
; stacksize=8
opt 0
opt NQLPSMRBT
Единственное различие - это две инструкции lea, которые, если память не изменяет, составляют максимум 16 циклов.Должно быть, что-то еще происходит с самой функцией, но по какой-то причине ее код запутан в ASM (в ASM всего 6 строк, никаких переходов, никаких других меток, ничего). Я продолжу искать ASM, где именно находится код.
1 ответ:
Как я и подозревал, есть момент, связанный с компилятором monster-WTF. Причина, по которой в случае локальных переменных было всего 6 строк кода, заключается в том, что компилятор смог вычислить, что эти 120 строк кода C на самом деле ничего не делают на глобальном уровне, поэтому он полностью игнорировал код ! То есть код ASM для метода был всего лишь этими 6 строками (с rts). Это, однако, не имеет большого смысла с результатами бенчмарка, которые я получил (но это будет по-другому рассказ)
Мораль этой истории: сделав переменную глобальной, компилятор фактически потрудился создать код для функции (а не просто пустой 6-ОП заглушку). И поскольку я встроил все внутри этой функции, не было никаких вложенных вызовов функций. Это, очевидно, звучит смешно, учитывая, что у меня было около 25 сеансов отладки с переменными и выводом на целевом устройстве. Но в тот момент, когда я удалил эти внешние вызовы печати / отладки, это, должно быть, был момент компилятора вообще не создавал функциональный код. А-кровавый мазинг....