ESP32-P4 memcpy эксперимент

Пытался тут ускорить копирование памяти на ESP32-P4.

Там есть стандартный memcpy() и реализованный через SIMD, наподобие этого .

Но для самообразования решил запилить свой, нестандартный.

В datasheet на ESP32-P4 описан регистр-аккумулятор QACC, который имеет ни много ни мало - 512 бит унутре. И присутствуют ассемблерные инструкции которые напрямую загружают из памяти в этот регистр и сохраняют в память тоже напрямую. (без использования промежуточных 128битовых регистров Q0-Q7). Его и было решено использовать, вместо 4х 128 битных регистров (версия memcpy от Espressif)

Тест приближен к реальности: буфер размером 64КБ копируется 1024 раза в цикле.

Скорость копирования : ~690 МБайт в секунду. Это не быстрее, чем SIMD код от Espressif для этого же процессора, но он зато раза в 4-5 короче.

Вот код, может кому-то нужен будет когда нибудь. Длина буфера должна быть кратна 64 байтам. Выравнивание - 16 байт.

Код надо положить в файл с расширением .S и кинуть в каталог с вашим проектом.

.text
.align 2
.global p4_memcpy
.type   p4_memcpy, @function
.balign 4
.option norvc

p4_memcpy:

    # a0: void *store_ptr
    # a1: const void *load_ptr
    # a2: const int length(bytes)

    # t0: счетчик циклов для аппаратного цикла 

    srli t0, a2, 6                   # count = length / 64
    esp.lp.setup 0, t0, .Lend        # заряжаем наш for(i=0; i<t0; i++)

    esp.ld.qacc.h.h.128.ip  a1, 16   # загружаем QACC, a1 += 16
    esp.ld.qacc.h.l.128.ip  a1, 16   # загружаем QACC, a1 += 16
    esp.ld.qacc.l.h.128.ip  a1, 16   # загружаем QACC, a1 += 16
    esp.ld.qacc.l.l.128.ip  a1, 16   # загружаем QACC, a1 += 16

    esp.st.qacc.h.h.128.ip  a0, 16  # выгружаем
    esp.st.qacc.h.l.128.ip  a0, 16
    esp.st.qacc.l.h.128.ip  a0, 16
.Lend:
    esp.st.qacc.l.l.128.ip  a0, 16  # последняя инструкция в аппаратном цикле

    ret

Использовать в своем Си++ коде так:

extern "C" void p4_memcpy(void *store_ptr, const void *load_ptr, const int length);
...
...
p4_memcpy(Dst, Src, Length_In_Bytes);

Вроде ничего не напутал.

Какбы, для ESP это неактуально, памойму. :slight_smile:

Ну надо же было какие-то отличия привести в свою сторону :-\

В прошлой ветке вы писали про проблемы с FREeRTOS. Я так понимаю, здесь они тоже будут?

Под проблемами я имел ввиду (надеюсь), что ежели две задачи одновременно будут делать p4_memcpy(), то таки да, т.к. регистры, которые используются никто никуда не сохраняет. Если шедулер переключится на другую задачу, которая ТОЖЕ делает в этот момент p4_memcpy(), то эта вторая задача испортит регистры важные. Потом шедулер переключится на первую задачу, а там уже ой, в регистрах совсем не то, что надо.

В коде выше такой регистр только один - qacc. Остальные регистры, которые используются в коде (a0, a1, t0) по ABI не надо сохранять.

QACC регистр (тот, что используется в коде выше) не используется FreeRTOS. Он не используется GCC. Про него знает только программист, который решил написать какой-то изврат матричный. Регистр этот для dot product придуман, вроде.

В нормальных RTOS, например, в RTEMS, когда вы создаете задачу (поток\процесс), то вы указываете, помимо всего прочего: задача юзает FPU или нет?. Задача юзает расширенные регистры или нет? и т.п. Так - правильно.

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

Надо отдать должное, FreeRTOS с патчами от Espressif умеет делать lazy context switch для FPU - сама понимает, что задача пользовалась FPU и сохранаяет восстанавливает FPU registers.

Почему бы не сохранять сразу все регистры и незаморачиваться? Потому что их больно дофига. 1 регистр на 512 бит. 8 регистров по 128 бит. Это не считая обычных 32битовых, которых там 32 штуки. Было принято решение не включать здоровенные регистры в context save

Мучайтесь сами, дорогие программисты. Ну или пишите все в одной задаче.