Самый быстрый опрос l oop - как мне урезать 1 цикл процессора? - PullRequest
8 голосов
/ 17 января 2020

В приложении реального времени¹ на ARM Cortex M3 (аналогичном STM32F101) мне нужно опрашивать бит регистра внутренней периферии, пока он не станет равным нулю, как можно теснее всего oop. Я использую битовую полосу для доступа к соответствующему биту. (Работающий) код C:

while (*(volatile uint32_t*)kMyBit != 0);

Этот код копируется в исполняемую на кристалле оперативную память. После некоторой ручной оптимизации2 опрос l oop сводится к следующему, что я рассчитал от 3 до 6 циклов:

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 D1FC      BNE      0x00600200

Как можно уменьшить неопределенность опроса? 5-тактный l oop будет соответствовать моей цели: сэмплировать этот бит как можно ближе к 15,5 циклам после того, как он достигнет нуля.

My spe c требует надежного обнаружения низкого импульса, по крайней мере, 6,5 тактовых циклов процессора ; надежно классифицировать его как короткий, если он длится менее 12,5 циклов; и надежно классифицировать его так долго, если он длится более 18,5 циклов. Импульсы не имеют определенной фазовой зависимости с тактовой частотой процессора, что является моей единственной точной временной привязкой. Для этого требуется максимум 5-часовой опрос l oop. На самом деле я эмулирую код, который работал на 8-битном процессоре, которому уже несколько десятилетий, который мог опрашивать с 5-тактовым циклом, и то, что он сделал, превратилось в spe c.


Я пытался чтобы компенсировать выравнивание кода, вставив NOP перед l oop, во многих вариантах, которые я пробовал, но никогда не наблюдал изменений.

Я пытался инвертировать CMP и LDR, но все равно получаю 6 циклов:

0x00600200 681A      LDR      r2,[r3,#0x00]
; we loop here
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FC      BNE      0x00600202

Это 8 циклов

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 681A      LDR      r2,[r3,#0x00]
0x00600204 2A00      CMP      r2,#0x00
0x00600206 D1FB      BNE      0x00600200

Но это 9 циклов:

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FB      BNE      0x00600200

¹ Измерение, сколько времени бит низок в контексте, где прерывание не происходит.

² Первоначально сгенерированный компилятором код использовал r12 в качестве регистра назначения, и это добавило 4 байта кода к l oop, стоимостью 1 цикл.

³ Указанные числа получены с предположительно точным циклом эмулятора STIce в реальном времени и его триггера эмулятора с функцией чтения по адресу регистра. Ранее я пробовал счетчик «States» с точкой останова в l oop, но результат зависит от местоположения точки останова. Одноступенчатый еще хуже: он всегда дает 4 цикла для LDR, когда, по крайней мере, иногда до 3.

Ответы [ 2 ]

8 голосов
/ 23 января 2020

Если я правильно понимаю вопрос, нужно сокращать не обязательно циклы l oop, а количество циклов между последовательными выборками (т. Е. Инструкциями LDR). Но может быть более одного LDR на одну итерацию. Вы можете попробовать что-то вроде этого:

    ldrb    r1, [r0]

loop:
    cbz     r1, out
    ldrb    r2, [r0]
    cbz     r2, out
    ldrb    r1, [r0]
    b       loop

out:

Интервал между двумя инструкциями LDRB варьируется, поэтому сэмплы не располагаются равномерно.

Это может немного задержать выход из l oop , но из описания проблемы я не могу сказать, важно это или нет.

У меня, случается, есть доступ к модели M7 с точностью до цикла, и когда процесс стабилизируется, ваш исходный l oop работает на M7 в 3 цикла на итерацию (имеется в виду LDR каждые 3 цикла), в то время как предложенный l oop выше работает в 4 циклах, но теперь есть два LDR (так что LDR каждые 2 цикла). Скорость выборки определенно улучшена.

Чтобы дать кредит, развертывание с CBZ в качестве перерыва было предложено @ Питером Кордесом в комментарии .

По общему признанию, M3 будет медленнее, но Это все еще стоит попробовать, если вам нужна частота дискретизации.

Также вы можете проверить, что LDRB вместо LDR (как в коде выше) что-то меняет, хотя я этого не ожидаю.

UPD: у меня есть другая версия 2-LDR l oop, которая на M7 завершается за 3 цикла, которые вы можете попробовать из интереса (также разрывы CBZ позволяют легко балансировать пути после l oop ):

    ldr     r1, [r0]

loop:
    ldr     r2, [r0]
    cbz     r1, out_slow
    cbz     r2, out_fast
    ldr     r1, [r0]
    b       loop

out_fast:
    /* NOPs as required */

out_slow:
1 голос
/ 23 января 2020

Вы можете попробовать это, но я подозреваю, что это даст те же 6 циклов

0x00600200 581a      LDR      r2,[r3,r0]; initialize r0 to 0x0
0x00600202 4282      CMP      r2,r0
0x00600204 D1FC      BNE      0x00600200
...