Как глобализация двух небольших массивов может привести к значительному падению производительности?

У меня есть 2 небольших локальных массива:

short int xpLeft [4], xpRight [4];

В тот момент, когда я делаю их глобальными (для доступа к ним другим способом, но только в пределах одного файла C (например, недоступного для других модулей)), производительность (на Motorola 68000) падает. Вместо 224 vblanks (для локальных) весь тест (рендеринг 320 кадров сцены) внезапно занимает 249 vblanks (глобальный массив)!

Что я пробовал:
Поскольку данные в массивах не использовались в этой функции, я подумал, что компилятор уловил это и не стал записывать полученное значение (из регистра) в память (чрезвычайно медленная операция на 68000 - доступ к памяти). Итак, я добавил небольшой код для использования этих значений массива в конце функции, что соответственно повысило затраты на производительность (всего 1 пустая строка).

Что могло бы помочь:
Мне нужно изучить окончательный код ASM (и сравнить обе версии), но я не знаю, как это сделать с помощью компилятора vbcc (от доктора Волкера). Я попробовал несколько переключателей из документации, и, хотя они действительно давали некоторый промежуточный результат, мне не удалось получить полный список ASM для каждого модуля (с именами функций из C).

У меня только что заработал переключатель "-k". По-видимому, порядок переключателей имеет значение, и я нашел место в командной строке, где он был распознан, и я наконец получил вывод * .ASM (не менее 300 тысяч строк), но наконец-то у меня что-то есть (ASM с символами) копаться.

Я думаю, что происходит:

  1. Создание глобальных массивов помещает их в разные адреса в ОЗУ, и контроллер памяти должен иметь доступ к другому банку, а переключение банка - чрезвычайно медленная операция на целевой платформе, что приводит к циклам зарядки RAS (для доступа к разным строкам адресов).
  2. псевдонимы указателя - возможно, компилятор генерирует другой код и может фактически получить доступ к фактической памяти для промежуточных результатов - но если бы у меня был вывод 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 (всего 6 строк в ASM, никаких переходов, никаких других меток, ничего). Я продолжу искать в ASM, где именно находится код.


person 3D Coder    schedule 23.09.2015    source источник
comment
Код, очевидно, переходит в другой режим адресации. (от SP-относительного до абсолютного)   -  person wildplasser    schedule 23.09.2015
comment
Да, но стек (на платформе) помещается в конец ОЗУ, что означает, что каждый отдельный доступ к переменным стека приводит к этим дорогостоящим циклам RAS (если только ваш код не приводит к этой последней странице ОЗУ, чего не происходит). , поскольку он находится в начале ОЗУ). Хотя я не уверен, в чем разница в циклах между этими двумя режимами адресации (я могу посмотреть), я сомневаюсь, что это может привести к такому падению производительности.   -  person 3D Coder    schedule 23.09.2015
comment
[Я не знаю 68K команд синхронизации / режимов адресации] по крайней мере последовательность команд (+ декодирование) может быть короче для адресации относительно SP.   -  person wildplasser    schedule 23.09.2015
comment
Я только что посмотрел в таблице циклов, и move.w занимает от 16 (лучших) до 24 (худших) циклов. Поскольку этот метод выполняется только 320 * 3 = 960 раз, разница должна быть, в худшем случае, 960 * (24-16) = 7680 циклов. Но это на ~ 3-4 порядка больше!   -  person 3D Coder    schedule 23.09.2015
comment
Это также нельзя объяснить циклами предварительной зарядки RAS. Компилятор, должно быть, делает что-то жестокое. Я почти уверен, что если бы я увидел результирующий код ASM, это был бы момент монстра-WTF. Я собираюсь снова попробовать эти переключатели компилятора, может быть, попробую несколько разных вариантов оптимизации в компиляторе.   -  person 3D Coder    schedule 23.09.2015
comment
Кстати: индексация тоже может быть разной для разных режимов адресации.   -  person wildplasser    schedule 23.09.2015
comment
правда, но с точки зрения циклов, как я вычислил выше, мы говорим о макс. 8000 циклов разницы. И даже если раньше массивы просто хранились в регистрах (например, компилятор заметил, что они не были затронуты приведенным ниже кодом, поэтому он никогда не потрудился записать результат на фактический адрес памяти - что является огромным, если), это просто удвойте разницу в количестве циклов до 16-20 000 (поскольку move.w с регистрами по-прежнему занимает не менее 12 циклов).   -  person 3D Coder    schedule 23.09.2015


Ответы (1)


Как я и подозревал, есть момент, связанный с компилятором Monster-WTF. Причина, по которой в случае локальных переменных было всего 6 строк кода, заключается в том, что компилятор смог выяснить, что эти 120 строк кода C на самом деле ничего не делают на глобальном уровне, поэтому он полностью игнорировал код все вместе! Это означает, что код ASM для метода состоял только из этих 6 строк (с rts). Однако это не имеет большого смысла с результатами тестов, которые я получил (но это уже другая история).

Мораль истории: сделав переменную глобальной, компилятор фактически потрудился создать код для функции (а не просто пустую заглушку на 6 операций). И поскольку я встроил все в эту функцию, не было вложенных вызовов функций. Это, очевидно, звучит нелепо, учитывая, что у меня было около 25 сеансов отладки с переменными и выводом на целевом устройстве. Но в тот момент я удалил эти внешние вызовы печати / отладки, которые, должно быть, были в тот момент, когда компилятор вообще не создавал код функции. Кровавый лабиринт ....

person 3D Coder    schedule 23.09.2015