Непонятки с оптимизацией и временем выполнения STM32

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015
Имеется следующий код:
#define LCD_CS        0x1000 // PB12       
#define LCD_WR        0x4000 // PB14
#define DATA_MASK     0x07f8 // PB3-PB10

void LCD_Clear(unsigned int c)
{	
  GPIOB_BASE->BRR = LCD_CS;
  Address_set(0,0,239,319);
  for(unsigned int i=0; i<(320*240); i++){  //  для  1 пикселя за итерацию
      GPIOB_BASE->ODR = 0xa803 | ((c >> 5) & DATA_MASK); \
      GPIOB_BASE->BSRR = LCD_WR;                         \
      GPIOB_BASE->ODR = 0xa803 | ((c << 3) & DATA_MASK); \
      GPIOB_BASE->BSRR = LCD_WR;                         \
  }
  GPIOB_BASE->BSRR = LCD_CS;
}

 

 
Компилируется так:
080024b0 <_Z9LCD_Clearj>:
 80024b0:	4b14      	ldr	r3, [pc, #80]	; (8002504 <_Z9LCD_Clearj+0x54>)
 80024b2:	b510      	push	{r4, lr}
 80024b4:	f44f 5280 	mov.w	r2, #4096	; 0x1000
 80024b8:	4604      	mov	r4, r0
 80024ba:	2000      	movs	r0, #0
 80024bc:	615a      	str	r2, [r3, #20]
 80024be:	4601      	mov	r1, r0
 80024c0:	22ef      	movs	r2, #239	; 0xef
 80024c2:	f240 133f 	movw	r3, #319	; 0x13f
 80024c6:	f7ff fe55 	bl	8002174 <_Z11Address_setjjjj>
 80024ca:	0961      	lsrs	r1, r4, #5
 80024cc:	00e0      	lsls	r0, r4, #3
 80024ce:	f401 61ff 	and.w	r1, r1, #2040	; 0x7f8
 80024d2:	f400 60ff 	and.w	r0, r0, #2040	; 0x7f8
 80024d6:	f441 4128 	orr.w	r1, r1, #43008	; 0xa800
 80024da:	f440 4028 	orr.w	r0, r0, #43008	; 0xa800
 80024de:	f041 0103 	orr.w	r1, r1, #3
 80024e2:	f040 0003 	orr.w	r0, r0, #3
 80024e6:	f44f 3296 	mov.w	r2, #76800	; 0x12c00
 80024ea:	4b06      	ldr	r3, [pc, #24]	; (8002504 <_Z9LCD_Clearj+0x54>)
 80024ec:	f44f 4480 	mov.w	r4, #16384	; 0x4000
 80024f0:	3a01      	subs	r2, #1
 80024f2:	60d9      	str	r1, [r3, #12]
 80024f4:	611c      	str	r4, [r3, #16]
 80024f6:	60d8      	str	r0, [r3, #12]
 80024f8:	611c      	str	r4, [r3, #16]
 80024fa:	d1f6      	bne.n	80024ea <_Z9LCD_Clearj+0x3a>
 80024fc:	f44f 5280 	mov.w	r2, #4096	; 0x1000
 8002500:	611a      	str	r2, [r3, #16]
 8002502:	bd10      	pop	{r4, pc}
 8002504:	40010c00 	andmi	r0, r1, r0, lsl #24

 

 
Судя по строке 80024fa, тело цикла начинается со строки 80024ea (а счетчик бежит в обратном по отношению к Си-коду направлении).
И здесь сразу несколько непоняток:
1. Понятно, что строки 80024ca-80024e2 вынесены оптимизатором за тело цикла, но почему в теле цикла остались строки 80024ea-80024ec?
2. Если исходить из того, что процессор работает на частоте 72 МГц, то цикл выполняется 22 такта, из которых 8 приходится на строки 80024f2-80025f8. Почему так много? Где-то читал, что у STM32 приходится в среднем 1.2 такта на команду.
a5021
Offline
Зарегистрирован: 07.07.2013

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Нет, ну на 8 тактов на 4 команды работы с периферией - это ладно. Но почему на 4 команды 80024ea, 80024ec, 80024f0 и 80024fa приходится 14 тактов? Да и команда 80024f0 никак не похожа на ту, что должна выполняться более 1 такта...

Опять же, остается вопрос: почему оптимизатор не вынес за пределы цикла 80024ea и 80024ec, и как с этим бороться?

ssss
Offline
Зарегистрирован: 01.07.2016

Желаете оптимальности??? Так и пишите по человечески... а не как попало...

Ну вот что это за хрень???

09
	  for(unsigned int i=0; i<(320*240); i++){  //  для  1 пикселя за итерацию
10
	      GPIOB_BASE->ODR = 0xa803 | ((c >> 5) & DATA_MASK); \
11
	      GPIOB_BASE->BSRR = LCD_WR;                         \
12
	      GPIOB_BASE->ODR = 0xa803 | ((c << 3) & DATA_MASK); \
13
	      GPIOB_BASE->BSRR = LCD_WR;                         \
14
	  }
15
	  GPIOB_BASE->BSRR = LCD_CS;

Какого лысого фигачить коктэйль из БССР и ОДР??? Ядру больше делать нечего... как распутывать ваши клубки сознания??? Почему не юзать только БССР???

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Разбирали уже давно, что конкретно STM32F103 нормально (без тактов ожидания) работает с флеш на частоте чуть ли не 24Мгц. При тактовой 72Мгц на каждую команду будет 3 такта ожидания. Но не похоже что так. Больше похоже что работа с памятью идет по 2 такта.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

ssss пишет:
Почему не юзать только БССР???
Так нельзя юзать то ,что умерло в 1991 году. Или же вы предлагаете перейти в некромантов.

a5021
Offline
Зарегистрирован: 07.07.2013

Кстати, каково назначение обратного слеша на концах строк внутри цикла?

andriano пишет:
Опять же, остается вопрос: почему оптимизатор не вынес за пределы цикла 80024ea и 80024ec, и как с этим бороться?

По умолчанию там вроде оптимизация по размеру. Возможно такой код лучше соответствует данной стратегии.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ssss пишет:

Какого лысого фигачить коктэйль из БССР и ОДР??? Ядру больше делать нечего... как распутывать ваши клубки сознания??? Почему не юзать только БССР???

Ну, как справедливо заметил Квон, БССР давно нет, а если Вы имеете в виду BSRR (БСРР), то для его формирования нужно гораздо больше битовых операций, т.к. часть битов нужно установить, а часть, наоборот, снять. Больше операций - это безусловный минус, а какой Вы видите плюс?

BSRR удобно использовать, когда некоторые биты дожны остаться в прежнем состоянии, и только часть нужно изменить. В этом случае BSRR позволяет отказаться от чтения из регистра. У меня же можно заменить сразу все, ничего не сохраняя, на чем я экономлю значительное количество операций. А в чем видите преимущество использования BSRR в данном случае Вы?

Приведите Ваш вариант этого фрагмента, благо он короткий.

a5021
Offline
Зарегистрирован: 07.07.2013

andriano пишет:
BSRR удобно использовать, когда некоторые биты дожны остаться в прежнем состоянии, и только часть нужно изменить. В этом случае BSRR позволяет отказаться от чтения из регистра. У меня же можно заменить сразу все, ничего не сохраняя, на чем я экономлю значительное количество операций. А в чем видите преимущество использования BSRR в данном случае Вы?

Вот не надо бы так делать. Даже если в данном случае все работает. Вместо статического состояния получается двойной импульс.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

a5021 пишет:

andriano пишет:
BSRR удобно использовать, когда некоторые биты дожны остаться в прежнем состоянии, и только часть нужно изменить. В этом случае BSRR позволяет отказаться от чтения из регистра. У меня же можно заменить сразу все, ничего не сохраняя, на чем я экономлю значительное количество операций. А в чем видите преимущество использования BSRR в данном случае Вы?

Вот не надо бы так делать. Даже если в данном случае все работает. Вместо статического состояния получается двойной импульс.

Это как?

a5021
Offline
Зарегистрирован: 07.07.2013

ODR взводит бит, BSRR сбрасывает. В худшем случае это повторяется дважды.

ssss
Offline
Зарегистрирован: 01.07.2016

andriano пишет:

если Вы имеете в виду BSRR (БСРР), то для его формирования нужно гораздо больше битовых операций, т.к. часть битов нужно установить, а часть, наоборот, снять. Больше операций - это безусловный минус, а какой Вы видите плюс?

Какой ещё плюс-минус??? У вас ОДР... неатомарность... RMW... Ну добавьте операций с портом В в прерывании... и ваша ёлочка облысеет вместе с вами... У вас уже есть маска... и так уже много операций... Если вам нужна скорость... и вы прикипели к ОДР... то двигайте шину данных дисплея в младшую половину порта... или в старшую... целиком... без всех этих масок... И обращайтесь к шине как к физической половине порта...тогда будет всё пучком...

Цитата:

BSRR удобно использовать, когда некоторые биты дожны остаться в прежнем состоянии, и только часть нужно изменить. В этом случае BSRR позволяет отказаться от чтения из регистра. У меня же можно заменить сразу все, ничего не сохраняя, на чем я экономлю значительное количество операций.

Запомните хорошо эту фразу... А вам разве... не это нужно??? BSRR позволяет и групповые операции с битами... Причём позволяет сбросить и установить несколько бит одновременно... одной командой... Попробуйте оба варианта... И даже если количество операций будет одинаковым... то операции с BSRR всё равно будут более предпочтительными и более правильными... На самом деле... код уменьшится... отвалят операции с ОДР и загрузкой-выгрузеой регистров АЛУ...

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

a5021 пишет:

ODR взводит бит, BSRR сбрасывает. В худшем случае это повторяется дважды.

И в каком случае это может иметь место? Приведите пример, когда реализуется этот худший случай.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ssss пишет:

Какой ещё плюс-минус??? У вас ОДР... неатомарность...

Откуда в ОДР неатомарность? Можно ссылочку?

Цитата:

Если вам нужна скорость... и вы прикипели к ОДР... то двигайте шину данных дисплея в младшую половину порта... или в старшую... целиком... без всех этих масок...

К сожалению, мне нужно переписывать 8 битов из PA в PB или наоборот. Но при этом PA11-PA12 заняты USB, PA13-PA14 заняты ST-Link, а PB2 занят BOOT1. Часть старшей половины занята в регистре PA, а часть младшей - в PB. И без сдвижек лично у меня не получается.

Цитата:

И обращайтесь к шине как к физической половине порта...тогда будет всё пучком...

А вот это уже интересно. Я почему-то считал, что в "железо" можно писать только порт целиком (ODR), отдельные биты (bit banding) либо устанавливать некоторые биты в 0 или единицу (BSRR, BRR). Как можно писать только в старший или толко в младший байты порта, чтобы это привордило к единственной операции записи на уровне железа? ПОкажите пример.

Цитата:

Запомните хорошо эту фразу... А вам разве... не это нужно??? BSRR позволяет и групповые операции с битами... Причём позволяет сбросить и установить несколько бит одновременно... одной командой... Попробуйте оба варианта... И даже если количество операций будет одинаковым... то операции с BSRR всё равно будут более предпочтительными и более правильными... На самом деле... код уменьшится... отвалят операции с ОДР и загрузкой-выгрузеой регистров АЛУ...

Давайте на конкретном примере. Вот есть две строчки:

      GPIOB_BASE->ODR = 0xa803 | ((c >> 5) & DATA_MASK); \
      GPIOB_BASE->BSRR = LCD_WR;                         \

они транслируются в:

0961       lsrs	r1, r4, #5
f401 61ff  and.w	r1, r1, #2040	; 0x7f8
f441 4128  orr.w	r1, r1, #43008	; 0xa800
f041 0103  orr.w	r1, r1, #3
4b06       ldr	r3, [pc, #24]	; (8002504 <_Z9LCD_Clearj+0x54>)
f44f 4480  mov.w	r4, #16384	; 0x4000
60d9       str	r1, [r3, #12]
611c       str	r4, [r3, #16]

Приведите свой вариант, который делает то же самое, но является более предпочтительным.

a5021
Offline
Зарегистрирован: 07.07.2013

andriano пишет:
И в каком случае это может иметь место? Приведите пример, когда реализуется этот удший случай.

Когда в ODR пишется значение с единицей в 14-м разряде. Упрощенно:

      GPIOB_BASE->ODR = (1 << 14) | ...;
      GPIOB_BASE->BSRR = (1 << 14);
      GPIOB_BASE->ODR = (1 << 14) | ...;
      GPIOB_BASE->BSRR = (1 << 14);

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

a5021 пишет:

Кстати, каково назначение обратного слеша на концах строк внутри цикла?

Пробовал разные варианты развернутого цикла с использованием #define. Оттуда и остался.

Цитата:

По умолчанию там вроде оптимизация по размеру. Возможно такой код лучше соответствует данной стратегии.

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

Правда, результаты немного странные:

1. Вариант -debug оказался быстрее, чем режим по умолчанию (Smallest).

2. Из трех режимов оптимизации лучшим оказался "самый слабый" (fast -O1), только он выкинул ненужные присвоения из цикла. Другие (-O2 и -O3) с этим не справились.

В общем, пока наилучший вариант заливки, когда в цикле единственный пиксель, дает 13 тактов на пиксель, что соответствует для экрана 320х240 fps=72.

Спасибо за подсказку.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

a5021 пишет:

andriano пишет:
И в каком случае это может иметь место? Приведите пример, когда реализуется этот удший случай.

Когда в ODR пишется значение с единицей в 14-м разряде. Упрощенно:

      GPIOB_BASE->ODR = (1 << 14) | ...;
      GPIOB_BASE->BSRR = (1 << 14);
      GPIOB_BASE->ODR = (1 << 14) | ...;
      GPIOB_BASE->BSRR = (1 << 14);

 

Попробовал:

void setup()
{
  GPIOB_BASE->CRL = 0x44444444;
  GPIOB_BASE->CRH = 0x43334444;
  while(1) {
      GPIOB_BASE->ODR  = (1 << 14) | 0x3000; // bit: 12, 13, 14
      GPIOB_BASE->BSRR = (1 << 14);
      GPIOB_BASE->ODR  = (1 << 14) | 0x1000; // bit: 12, 14
      GPIOB_BASE->BSRR = (1 << 14);
  }

Ничего подозрительного не заметил - не совсем симметричный прямоугольник с частотой около 6.5 МГц, т.е. 11 тактов на период.

a5021
Offline
Зарегистрирован: 07.07.2013

andriano пишет:
идея попробовать различные алгоритмы оптимизации оказалась плодотворной.

Есть еще

 -Ofast

    Disregard strict standards compliance. -Ofast enables all -O3 optimizations. It also enables optimizations that are not valid for all standard-compliant programs. It turns on -ffast-math and the Fortran-specific -fstack-arrays, unless -fmax-stack-var-size is specified, and -fno-protect-parens.
 

 
ssss
Offline
Зарегистрирован: 01.07.2016

Загрузил вашу шнягу у себя в Кейл... и не понял... А за что вы там собственно давитесь??? У вас компиль и так всё сделал по максимуму... бело и пушисто... вынес всё лишнее за тело цикла.. даже инкремент заменил по фэншую на декремент...

80024f0:   3a01        subs    r2, #1

У себя я сразу цикл сделал на декременте... Получилось четыре записи в порт... декремент и переход... Итого 12 тактов... Куда ещё меньше??? Хотите ещё меньше... разворачивайте цикл... немножко сэкономите на переходах...

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ssss, в финальном варианте у меня 13 тактов на итерацию, а в первоначальном было 22, из-за чего и поднял вопрос.

И, кстати, Вашего варианта кода с учетом замечаний в постах №3, №7, №11 и №13 я так и не увидел. Сказали "А", говорите "Б".

ssss
Offline
Зарегистрирован: 01.07.2016

А он мне нужен... этот код варианта??? RM и K&R вам в помощь... Не знаете как обратиться к слову, полуслову или байту??? Так тренируйтесь... а то в F0 или F3 ещё и нарвётесь на неприятность с данными в SPI... Или не знаете... что у BSRR приоритет установки перед сбросом??? В RM об этом чёрным по белому написано... Или не знаете... что операции с ODR неатомарны... а с BSRR - атомарны??? Вам лень читать??? А мне лень писать... такова жизнь...