От чего может зависеть быстродействие программы?

Любые разговоры которые хоть как-то связаны с тематикой форума

Модератор: Модераторы разделов

MiK13
Сообщения: 1230
ОС: Linux Debian

От чего может зависеть быстродействие программы?

Сообщение MiK13 »

Есть компьютер. От XILINX, Точнее, какой-то китайский аналог.
Процессор Ultrascale. Содержит 4 ядра Cortex A53 и 2 ядра "реального времени" R5. Памяти 2 ГиБ. И ещё ПЛИС для обработки сигналов.
Система Petalinux. Для неё память ограничена:

Shell

# cat /proc/cmdline
earlycon console=ttyPS0,115200 clk_ignore_unused root=/dev/mmcblk1p2 mem=1792M rw rootwait
Старшие 256М используются для передачи данных от ПЛИС (не разобрались как из ПЛИС по DMA передавать данные в Linux, а поток большой).
Написал простейшую программу для запуска на процессоре R5:

Код: Выделить всё

volatile int *a=(int *)0x70008000;

int main() {
  while(1)
    (*a)++;
}
И параллельно вариант для Linux, в котором значение для a определяется:

Код: Выделить всё

  if(argc==1) {
    int dh=open("/dev/mem",O_RDWR|O_SYNC);
    a = mmap(NULL, 128, PROT_READ|PROT_WRITE, MAP_SHARED, dh, 0x70008000);
  } else {
    int id_md=shmget(100, 128, 0666 | IPC_CREAT));
    a=(int *)shmat(id_md,0,0);
  }
То есть при запуске без параметров a ссылается на ячейку с адресом 0x70008000, в иначе на ячейку в разделяемой памяти.
Для того, чтобы знать быстродействия инкремента ячейки написал программу с таким кодом:

Код: Выделить всё

#define MASK 0x00800000
  while(1) {
    do {
      vt=(*a)&MASK;
    } while(vt==vp);
    clock_gettime(0,&tt);
    dif=(tt.tv_sec-tp.tv_sec)+((tt.tv_nsec-tp.tv_nsec)*1e-9);
    printf(" %d %8.5f -> %.4f\n",!!vt,dif,MASK/dif/1000000);
    tp=tt; vp=vt;
  }
Программа выводит сколько миллионов раз в секунду меняется ячейка.
Получил такие результаты.
Программа, работающая на R5 инкриментирует ячейку примерно 34 млн. раз в секунду.
Та же программа, работающая под Linux делает это всего 6.2 млн.
Ячейку в разделяемой памяти этот же код инрементирует со скоростью 266 млн. раз в секунду. Разница почти в 43 раза.
Решил оптимизировать цикл (заменил b .L6 на b .L61 (то есть единица добавляется не к самой ячейке, а только к значению, которое записывается в эту ячейку):

Код: Выделить всё

.L6:
	adrp	x0, a
	add	x0, x0, :lo12:a
	ldr	x0, [x0]
	ldr	w1, [x0]
.L61:
	add	w1, w1, 1
	str	w1, [x0]
	b	.L61
Быстродействие программы под линуксом возросло с 6.2 до 98.6 млн в случае ячейки с абсолютным адресом и с 266 до 531 в случае ячейки в разделяемой памяти.
Аналогичная оптимизация программы для Cortex R5 дало увеличение быстродействия с 34 млн до практически 100.

Почему может быть такая разница в быстродействии?

P.S. Используемые компиляторы:
aarch64-linux-gnu-gcc для компиляции linux=программы для ядра A53 и arm-none-eabi-gcc для компиляции автономной программы для ядра R5 (компиляция напрямую не проходит, компилирую с -c, а потом собираю с помощью arm-none-eabi-ld)
Спасибо сказали:
Аватара пользователя
sungreen
Сообщения: 14
ОС: linux

Re: От чего может зависеть быстродействие программы?

Сообщение sungreen »

А если сравнить варианты по инструкциям, если отличия?
Спасибо сказали:
Аватара пользователя
Bizdelnick
Модератор
Сообщения: 21035
Статус: nulla salus bello
ОС: Debian GNU/Linux

Re: От чего может зависеть быстродействие программы?

Сообщение Bizdelnick »

Сколько уровней кеша у тех и других ядер?
Кстати, думается мне, что volatile тут может себя повести непредсказуемо. Атомики надо использовать.
Пишите правильно:
в консоли
вку́пе (с чем-либо)
в общем
вообще
в течение (часа)
новичок
нюанс
по умолчанию
приемлемо
проблема
пробовать
трафик
Спасибо сказали:
MiK13
Сообщения: 1230
ОС: Linux Debian

Re: От чего может зависеть быстродействие программы?

Сообщение MiK13 »

Bizdelnick писал:
05.01.2025 00:11
Сколько уровней кеша у тех и других ядер?
Я не знаю сколько уровней кэша у ядер, но подозреваю, что дело всё в диспетчере памяти.
Вот основной код цикла while(1) (*a)++;

Код: Выделить всё

.L6:					; 1 начало цикла
	adrp	x0, a			; 2 как я понимаю
	add	x0, x0, :lo12:a		; 3 в этих инструкциях берётся адрес из переменной a
	ldr	x0, [x0]		; 4 и по этому адрес берётся значение
	ldr	w1, [x0]		; 5 и загружается в w1
.L61:					;; 6 эту метку я добавил сам
	add	w1, w1, 1		; 7 к значению в w1 добавляется единица
	str	w1, [x0]		; 8 новое значение записывается по адресу *a, который находится в x0
	b	.L61			;; 9 компилятор поставил тут переход на .L6, я заменил на переход на .L61
Если я запускаю программу так, что переменная a указывает на ячейку в общей памяти:

Код: Выделить всё

    id_md=shmget(100, 128, 0666 | IPC_CREAT);
    a=(int *)shmat(id_md,0,0);
то исходный цикл выполняется чуть меньше, чем за 4 нс. А после модификации выполняется около 2 нс. Значит инструкции в строках 2...5 выполняются около 2 нс.
Если же я запускаю программу так, что переменная a ссылается на абсолютный адрес памяти (который не Linux использует):

Код: Выделить всё

    int dh=open("/dev/mem",O_RDWR|O_SYNC);
    a = mmap(NULL, 128, PROT_READ|PROT_WRITE, MAP_SHARED, dh, 0x70008000);
то на основной цикл (строки 1...9) уходит уходит примерно 160 нс. А после модификации, когда я исключаю из цикла строки 2...5 чуть больше 10 нс. То есть получается, что на выполнение строк 2...5 уходит примерно 150 нс.
По крайней мере у меня такое впечатление.
Bizdelnick писал:
05.01.2025 00:11
Атомики надо использовать.
Что за "Атомики"?
Спасибо сказали:
Аватара пользователя
Bizdelnick
Модератор
Сообщения: 21035
Статус: nulla salus bello
ОС: Debian GNU/Linux

Re: От чего может зависеть быстродействие программы?

Сообщение Bizdelnick »

MiK13 писал:
08.01.2025 17:55
Что за "Атомики"?
https://en.cppreference.com/w/c/atomic
Правда не знаю, как это в случае с rt-ядрами работает. Лучше, конечно, доку от производителя смотреть.
Пишите правильно:
в консоли
вку́пе (с чем-либо)
в общем
вообще
в течение (часа)
новичок
нюанс
по умолчанию
приемлемо
проблема
пробовать
трафик
Спасибо сказали:
MiK13
Сообщения: 1230
ОС: Linux Debian

Re: От чего может зависеть быстродействие программы?

Сообщение MiK13 »

Bizdelnick писал:
09.01.2025 12:10
https://en.cppreference.com/w/c/atomic
Правда не знаю, как это в случае с rt-ядрами работает. Лучше, конечно, доку от производителя смотреть.
Что-то сложное. Надо разбираться. Но не сейчас.

Я стал проверять работу программы под линуксом. Немного её модифицировал. Через параметр передаётся адрес ячейки памяти, который надо инкрементировать. Соответственно и программу, которая считает скорость инкремента.
Программа в двух вариантах: "чистый" while(1) (*a)++; (PL) и "модифицированный" (PLm), в котором я в цикле убрал команды загрузки значения ячейки из памяти. Доступ к ячейке через /dev/mem
Результаты (почему-то немного разные в разное время):
Если я задаю в качестве адреса 0x70008000 (который Linux не использует), то значения скорости получаются примерно 5М и 101М
Если я задаю в качестве адреса 0x60008000 (в пределах того, что отведено для Linux), то значения скорости получаются примерно 98М и 530М.
Пока предполагаю, что Linux как-то настраивает диспетчер памяти и данные "линукса" хорошо кэшируются.
А если адрес за пределами Linux, то кэширование не производится. И команды загрузки выполняются очень долго.

P.S. PL и PLm -- названия программ, которые инкрементируют ячейку
Спасибо сказали: