ARM: инструкция stmia намного быстрее, чем ldmia? - PullRequest
2 голосов
/ 06 сентября 2011

Я наблюдаю что-то, что просто не может быть правильным.

Я обнаружил, что могу писать в основную память на ARM, используя

 stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10}

примерно в 6 раз быстреечем я могу прочитать из основной памяти (SDRAM), используя

 ldmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10}

Таким образом, буквально, если я заменю ldmia на stmia, а r0 указывает на выделенный фрагмент памяти, stmia в 6 раз быстрее, если я рассчитываю время.

Есть ли что-то особенное в инструкции stmia ARM, которая может вызывать ложное срабатывание?Может быть, stmia странно взаимодействует с кешами?

В общем, я видел, что ARM может писать быстрее, чем читает из основной памяти, но в 6 раз быстрее не имеет смысла с аппаратной точки зрения.


Обновление: Это происходит с большими кусками памяти, которые, вероятно, больше, чем кэш L2.

Поэтому объяснение кэширования не имеет достаточного смысла.Даже если ARM имеет кэш L2 с обратной записью, на запись которого уходит вечность, он не будет больше, чем моя выделенная область памяти.

Спасибо.

Ответы [ 4 ]

3 голосов
/ 06 сентября 2011

Вы не указали свою платформу и среду, но я подозреваю, что вы видите эффект кэширования. При выполнении загрузки сначала проверяется кэш, и если данных там нет (отсутствует кэш), ЦП должен ждать, пока он фактически не будет извлечен из основной памяти, которая немного медленнее, чем кэш. Хранилище, с другой стороны, может просто выгружать данные в кеш и двигаться дальше, рассчитывая, что контроллер кеша выполнит реальную запись в память без участия ЦП.

Чтобы определить, так ли это, вы можете попробовать различные тестовые шаблоны, например ::100100

  1. Отключите кэширование, это должно привести к тому, что время будет отражать фактическое время доступа к памяти.
  2. Выполнить загрузку с одного и того же адреса несколько раз подряд. Повторные загрузки должны быть быстрее, так как они будут попадать в кеш.
  3. Выполнить загрузку сразу после хранилища по тому же адресу - это должно использовать значение из кэша и быть быстрее.
  4. Перед каждой операцией очищать кэш, чтобы уменьшить его влияние на время
  5. Добавьте DSB после хранилища, чтобы убедиться, что фактическая запись завершена до продолжения выполнения.

и так далее. Чтобы узнать больше о кэшировании в процессорах ARM, я бы рекомендовал прочитать Руководство программиста для серии Cortex-A *1018*. Это будет полезно, даже если ваш процессор не Cortex-A, так как большинство концепций применимо и к старшим поколениям.

1 голос
/ 07 сентября 2011

Вот пример кода:

.globl _start
_start:


    ldr r11,=0xD6800600
    mov r1,#0
    str r1,[r11,#0x08] ;@ stop timer
    mvn r1,#0
    str r1,[r11,#0x00] ;@ timer load register
    mov r1,#3
    str r1,[r11,#0x08] ;@ start timer, 1 x prescaler

    mov r3,#3
    mov r4,#4
    mov r5,#5
    mov r6,#6
    mov r7,#7
    mov r8,#8
    mov r9,#9
    mov r10,#10

    ldr r0,=0xD6001000
    mov r12,#0x400

    ldr r13,[r11,#0x04]
write_loop:
    stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10}
    subs r12,#1
    bne write_loop
    ldr r1,[r11,#0x04]

    ldr r0,=0xD6001000
    mov r12,#0x400

    ldr r2,[r11,#0x04]
read_loop:
    ldmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10}
    subs r12,#1
    bne read_loop
    ldr r14,[r11,#0x04]

    mov r0,r13
    mov r3,r14

    ldr sp,=0xD600E000
    bl notmain
hang: b hang

.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

Периферийная база 0xD6800000.Использование встроенного таймера.Это mpcore r2p0 (версия 2.0) с 64-битной шиной AXI.Вам может показаться интересным, что записи в 3 раза медленнее, чем чтения.распечатка времени и вычитание записи - это 0x191F5 тиков и чтение 0x81F7.

Но я знаю причину, почему.Этот контроллер памяти может / действительно превращает STM в 4 цикла записи двойных слов (что вы видите на шине AXI).Я думаю, что чтение - это передача из 8 слов (4 двойных слова), так что для записи в 4 раза увеличивается нагрузка шины.Накладные расходы плюс один такт для каждой записи, каждое чтение - это издержки плюс 8 тактов для передачи данных.

Я добавил код, чтобы убедиться, что кэши отключены.И добавил nops, по одному nop за раз, чтобы отрегулировать выравнивание инструкций в памяти (все еще может влиять на выборки и реализацию на чиповой памяти).Например, поместите nops перед чтением таймера перед каждым циклом.Управляя расположением циклов в памяти, цикл чтения был наиболее чувствительным, и я мог изменить результаты на 50% в любом случае.Вместо диапазона 0x8xxx он будет переходить к 0xAxxx в одном чувствительном месте и 0x6xxx в другом.Я не собираюсь углубляться в это, возможно, есть некоторые эффекты от самого плунжера, а также эффекты от ARM11 с некоторым сглаживанием при извлечении, а также, возможно, сглаженные чтения сглаживаются в меньшее количество совмещенных операций чтения и т. Д.

Я рекомендую вам сделать то же самое, выключить кэши, выключить MMU, отключить проверку четности или генерацию / проверку ecc.Затяните петли, время вне петли.Размещайте nops по одному перед тестовым кодом, по одному, возможно, целых 8, 16 или 32, если есть различия, шаблон должен повторяться на некоторой логической границе, такой как 4, 8 или 16 слов.Также, если ваш таймер дает вам другой номер (без перекомпиляции), запускаемый после запуска, у вас есть прерывания или что-то подобное, что может испортить ваши номера.Часы ядра процессора, подобные этим, я не могу себе представить, изменится даже на один такт, конечно, зависит от вашей системы памяти.Хммм, так как вы выполняете инструкции из 8 слов, попробуйте изменить начальный адрес на 0, 4, 8 и 12. Также попробуйте включить кэш инструкций (для этого не нужен mmu).Я подозреваю, что вы также увидите скачок показателей производительности.

Есть некоторые другие ARM, которые я могу попробовать ... Не чипы, о которых у меня столько внутренних знаний ...

Какая семья/ ядро ​​у тебя работает?Можете ли вы опубликовать свой тестовый код?Ваш тестовый цикл похож на то, что я делал выше?

0 голосов
/ 16 января 2012

Что вы заметили, так это эффект буфера записи ARM. Буфер записи находится между процессором и кешем или оперативной памятью. Он предназначен для предотвращения ожидания ЦП памяти при выполнении небольших наборов операций записи. Типичный буфер записи может обрабатывать 4 уникальных адреса по 2 слова, каждое из которых будет содержать регистры, которые вы записали в своем примере выше. Если вы создадите цикл stmia с 8 регистрами, вы начнете видеть истинную скорость, с которой он может писать. Объединение адресов не выполняется, поэтому, если вы записываете 4 байта, это также заполнит буфер записи.

0 голосов
/ 08 сентября 2011

@ dwelch Я думаю, что вы делаете интересное замечание о NOP, однако уже доказано, что код может работать очень быстро при чтении - при условии, что он обращается к кешам, а не к основной памяти. Скорость там составляет около 5000 МБ / с. Принимая во внимание, что запись на 6000 МБ / с. Таким образом, NOP на самом деле не объясняют, почему запись в основную память происходит намного быстрее, чем чтение из основной памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...