Тесты ARM Cortex A8: кто-нибудь может помочь мне разобраться в этих числах?

#assembly #arm #benchmarking #neon #cortex-a8

#сборка #arm #сравнительный анализ #neon #cortex-a8

Вопрос:

Я работаю над написанием нескольких алгоритмов DSP в реальном времени для Android, поэтому я решил запрограммировать ARM непосредственно в сборке, чтобы максимально оптимизировать все и сделать математику максимально легкой. Сначала я получал тесты скорости, которые не имели особого смысла, поэтому я начал читать об опасностях конвейера, возможностях двойной выдачи и так далее. Я все еще озадачен некоторыми получаемыми цифрами, поэтому размещаю их здесь в надежде, что кто-нибудь сможет пролить свет на то, почему я получаю то, что получаю. В частности, меня интересует, почему NEON требует разного количества времени для выполнения вычислений с разными типами данных, хотя он утверждает, что выполняет каждую операцию ровно за один цикл. Мои выводы таковы.

Я использую очень простой цикл для сравнительного анализа, и я выполняю его в течение 2 000 000 итераций. Вот моя функция:

 hzrd_test:

    @use received argument an number of iterations in a loop
    mov r3 , r0

    @come up with some simple values
    mov r0, #1
    mov r1, #2

    @Initialize some NEON registers (Q0-Q11)
    vmov.32 d0, r0, r1
    vmov.32 d1, r0, r1
    vmov.32 d2, r0, r1

    ...

    vmov.32 d21, r0, r1
    vmov.32 d22, r0, r1
    vmov.32 d23, r0, r1

hzrd_loop:

    @do some math
    vadd.s32 q0, q0, q1
    vadd.s32 q1, q0, q1
    vadd.s32 q2, q0, q1
    vadd.s32 q3, q0, q1
    vadd.s32 q4, q0, q1
    vadd.s32 q5, q0, q1
    vadd.s32 q6, q0, q1
    vadd.s32 q7, q0, q1
    vadd.s32 q8, q0, q1
    vadd.s32 q9, q0,s q1
    vadd.s32 q10, q0, q1
    vadd.s32 q11, q0, q1

    @decrement loop counter, branch to loop again or return
    subs r3, r3, #1
    bne hzrd_loop

    @return
    mov r0, r3
    mov pc, lr
  

Обратите внимание на вычислительную операцию и тип данных, указанный как вектор add ( vadd ) и подписанный 32-разрядный int ( s32 ). Эта операция завершается в течение определенного времени (см. Таблицу результатов ниже). Согласно этому документу ARM Cortex-A8 и следующим страницам, почти все элементарные арифметические операции в NEON должны завершаться за один цикл, но вот что я получаю:

vmul.f32 ~ 62 мс 
vmul.u32 ~ 125 мс 
vmul.s32 ~ 125 мс

vadd.f32 ~ 63 мс 
vadd.u32 ~ 29 мс 
vadd.s32 ~ 30 мс

Я делаю это, просто заменяя операции и типы данных всего в вышеупомянутом цикле. Есть ли причина, по которой vadd.u32 в два раза быстрее, чем vadd.f32 и vmul.f32 в два раза быстрее, чем vmul.u32 ?

Приветствия! = )

Ответ №1:

Вау, ваши результаты ОЧЕНЬ точны :

  • умножение 32-битного целого числа Q занимает 4 цикла, в то время как float занимает 2.
  • 32-битное целое число Q add стоит 1 цикл, в то время как float занимает 2.

Хороший эксперимент.

Возможно, вы уже знаете, но будьте осторожны при написании кода для NEON :

  • не обращаться к памяти с помощью ARM, пока NEON выполняет тяжелую работу
  • не смешивайте инструкции VFP с инструкциями NEON. (за исключением тех, которые являются общими)
  • не обращаться к регистрам S.
  • не переносите регистры NEON на ARM

Все вышеперечисленное вызовет ОГРОМНЫЕ сбои.

Удачи!

PS: Я бы предпочел оптимизировать для A9 (немного отличающиеся тайминги цикла), поскольку практически все новые устройства поставляются с A9. И временная диаграмма A9 от ARM намного более удобочитаема. 🙂

Комментарии:

1. Что ж, судя по тому, как вы считаете циклы, мои данные определенно имеют смысл, но как вы их считаете? Очевидно, я неправильно читаю документацию, или мы читаем разные документы.

2. На самом деле мы читаем то же самое. Вам не нужно считать. Это просто на диаграмме в разделе «Циклы». ARM пытается показать нам слишком много и преуспел в том, чтобы просто запутать нас. Посмотрите на VMUL (целое число, обычный), прямо из «Qd, Qm, Qn» вы видите от 1 до 4. Это означает, что требуется 4 цикла, и справа от них вы можете видеть, что происходит на ЭТОМ этапе цикла выполнения. (на каком этапе конвейера ожидаются операнды src и на каком этапе помещаются операнды dst)

3. Я бы сформулировал это так: 4, Qd (5,3), Qn (4,2), Qm (3,1). Но это ARM.

4. Спасибо! = ) Документ меня действительно смутил.

Ответ №2:

Я собираюсь предположить (поскольку у меня нет под рукой ссылок на документы), что вы столкнулись с проблемами конвейера. Я знаю, что ошибка FPU, которая теперь называется VFPU, имеет другую длину конвейера, чем у CPU, для выполнения целочисленной математической части вашего цикла. Я вижу, что вторая арифметическая операция зависит от первой, которая остановит любой конвейер и, возможно, выявит различия, которые вы видите.

Кроме того, я считаю, что multiply — это не инструкция с 1 циклом для целых чисел, а 2-5 циклов в зависимости от msb 2-го значения — здесь 2 цикла из-за небольшого размера числа, что объясняет эту разницу. Чтобы убедиться в этом, начните с большего числа умножения и посмотрите, замедляется ли оно при большем размере.

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

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

Комментарии:

1. Я не на 100% уверен, что правильно читаю раздел 16.6 Cortex-A8 TRM (PDF) , но я думаю, вы правы насчет того, что некоторые инструкции занимают более 1 цикла.

2. Майкл, насколько велика одна страница кэша команд? Я никогда не обращал на это внимания до сих пор. Было бы неплохо знать.

3. Честно говоря, я не уверен. Это зависит от реализации — что-то, что вам придется искать для вашего конкретного устройства. Кстати, хороший ответ — ваш ответ объясняет большие различия, в то время как мой может объяснить меньшие различия.