Непонятки с оптимизацией и временем выполнения STM32
- Войдите на сайт для отправки комментариев
Сб, 18/08/2018 - 17:51
Имеется следующий код:
#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 такта на команду.
выполнение команды str может занимать от одного до трех тактов. выполнению предшествует выборка. с учетом, что флеш отдает данные медленнее, чем ядро может их перемалывать, задержки возможны и здесь.
Нет, ну на 8 тактов на 4 команды работы с периферией - это ладно. Но почему на 4 команды 80024ea, 80024ec, 80024f0 и 80024fa приходится 14 тактов? Да и команда 80024f0 никак не похожа на ту, что должна выполняться более 1 такта...
Опять же, остается вопрос: почему оптимизатор не вынес за пределы цикла 80024ea и 80024ec, и как с этим бороться?
Желаете оптимальности??? Так и пишите по человечески... а не как попало...
Ну вот что это за хрень???
Какого лысого фигачить коктэйль из БССР и ОДР??? Ядру больше делать нечего... как распутывать ваши клубки сознания??? Почему не юзать только БССР???
Разбирали уже давно, что конкретно STM32F103 нормально (без тактов ожидания) работает с флеш на частоте чуть ли не 24Мгц. При тактовой 72Мгц на каждую команду будет 3 такта ожидания. Но не похоже что так. Больше похоже что работа с памятью идет по 2 такта.
Кстати, каково назначение обратного слеша на концах строк внутри цикла?
По умолчанию там вроде оптимизация по размеру. Возможно такой код лучше соответствует данной стратегии.
Какого лысого фигачить коктэйль из БССР и ОДР??? Ядру больше делать нечего... как распутывать ваши клубки сознания??? Почему не юзать только БССР???
Ну, как справедливо заметил Квон, БССР давно нет, а если Вы имеете в виду BSRR (БСРР), то для его формирования нужно гораздо больше битовых операций, т.к. часть битов нужно установить, а часть, наоборот, снять. Больше операций - это безусловный минус, а какой Вы видите плюс?
BSRR удобно использовать, когда некоторые биты дожны остаться в прежнем состоянии, и только часть нужно изменить. В этом случае BSRR позволяет отказаться от чтения из регистра. У меня же можно заменить сразу все, ничего не сохраняя, на чем я экономлю значительное количество операций. А в чем видите преимущество использования BSRR в данном случае Вы?
Приведите Ваш вариант этого фрагмента, благо он короткий.
Вот не надо бы так делать. Даже если в данном случае все работает. Вместо статического состояния получается двойной импульс.
Вот не надо бы так делать. Даже если в данном случае все работает. Вместо статического состояния получается двойной импульс.
Это как?
ODR взводит бит, BSRR сбрасывает. В худшем случае это повторяется дважды.
если Вы имеете в виду BSRR (БСРР), то для его формирования нужно гораздо больше битовых операций, т.к. часть битов нужно установить, а часть, наоборот, снять. Больше операций - это безусловный минус, а какой Вы видите плюс?
Какой ещё плюс-минус??? У вас ОДР... неатомарность... RMW... Ну добавьте операций с портом В в прерывании... и ваша ёлочка облысеет вместе с вами... У вас уже есть маска... и так уже много операций... Если вам нужна скорость... и вы прикипели к ОДР... то двигайте шину данных дисплея в младшую половину порта... или в старшую... целиком... без всех этих масок... И обращайтесь к шине как к физической половине порта...тогда будет всё пучком...
BSRR удобно использовать, когда некоторые биты дожны остаться в прежнем состоянии, и только часть нужно изменить. В этом случае BSRR позволяет отказаться от чтения из регистра. У меня же можно заменить сразу все, ничего не сохраняя, на чем я экономлю значительное количество операций.
Запомните хорошо эту фразу... А вам разве... не это нужно??? BSRR позволяет и групповые операции с битами... Причём позволяет сбросить и установить несколько бит одновременно... одной командой... Попробуйте оба варианта... И даже если количество операций будет одинаковым... то операции с BSRR всё равно будут более предпочтительными и более правильными... На самом деле... код уменьшится... отвалят операции с ОДР и загрузкой-выгрузеой регистров АЛУ...
ODR взводит бит, BSRR сбрасывает. В худшем случае это повторяется дважды.
И в каком случае это может иметь место? Приведите пример, когда реализуется этот худший случай.
Какой ещё плюс-минус??? У вас ОДР... неатомарность...
Откуда в ОДР неатомарность? Можно ссылочку?
Если вам нужна скорость... и вы прикипели к ОДР... то двигайте шину данных дисплея в младшую половину порта... или в старшую... целиком... без всех этих масок...
К сожалению, мне нужно переписывать 8 битов из PA в PB или наоборот. Но при этом PA11-PA12 заняты USB, PA13-PA14 заняты ST-Link, а PB2 занят BOOT1. Часть старшей половины занята в регистре PA, а часть младшей - в PB. И без сдвижек лично у меня не получается.
И обращайтесь к шине как к физической половине порта...тогда будет всё пучком...
А вот это уже интересно. Я почему-то считал, что в "железо" можно писать только порт целиком (ODR), отдельные биты (bit banding) либо устанавливать некоторые биты в 0 или единицу (BSRR, BRR). Как можно писать только в старший или толко в младший байты порта, чтобы это привордило к единственной операции записи на уровне железа? ПОкажите пример.
Запомните хорошо эту фразу... А вам разве... не это нужно??? BSRR позволяет и групповые операции с битами... Причём позволяет сбросить и установить несколько бит одновременно... одной командой... Попробуйте оба варианта... И даже если количество операций будет одинаковым... то операции с BSRR всё равно будут более предпочтительными и более правильными... На самом деле... код уменьшится... отвалят операции с ОДР и загрузкой-выгрузеой регистров АЛУ...
Давайте на конкретном примере. Вот есть две строчки:
они транслируются в:
Приведите свой вариант, который делает то же самое, но является более предпочтительным.
Когда в ODR пишется значение с единицей в 14-м разряде. Упрощенно:
Кстати, каково назначение обратного слеша на концах строк внутри цикла?
Пробовал разные варианты развернутого цикла с использованием #define. Оттуда и остался.
По умолчанию там вроде оптимизация по размеру. Возможно такой код лучше соответствует данной стратегии.
Насчет стратегии - вряд ли, на сама идея попробовать различные алгоритмы оптимизации оказалась плодотворной.
Правда, результаты немного странные:
1. Вариант -debug оказался быстрее, чем режим по умолчанию (Smallest).
2. Из трех режимов оптимизации лучшим оказался "самый слабый" (fast -O1), только он выкинул ненужные присвоения из цикла. Другие (-O2 и -O3) с этим не справились.
В общем, пока наилучший вариант заливки, когда в цикле единственный пиксель, дает 13 тактов на пиксель, что соответствует для экрана 320х240 fps=72.
Спасибо за подсказку.
Когда в ODR пишется значение с единицей в 14-м разряде. Упрощенно:
Попробовал:
Ничего подозрительного не заметил - не совсем симметричный прямоугольник с частотой около 6.5 МГц, т.е. 11 тактов на период.
Есть еще
-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.
Загрузил вашу шнягу у себя в Кейл... и не понял... А за что вы там собственно давитесь??? У вас компиль и так всё сделал по максимуму... бело и пушисто... вынес всё лишнее за тело цикла.. даже инкремент заменил по фэншую на декремент...
80024f0: 3a01 subs r2, #1
У себя я сразу цикл сделал на декременте... Получилось четыре записи в порт... декремент и переход... Итого 12 тактов... Куда ещё меньше??? Хотите ещё меньше... разворачивайте цикл... немножко сэкономите на переходах...
ssss, в финальном варианте у меня 13 тактов на итерацию, а в первоначальном было 22, из-за чего и поднял вопрос.
И, кстати, Вашего варианта кода с учетом замечаний в постах №3, №7, №11 и №13 я так и не увидел. Сказали "А", говорите "Б".
А он мне нужен... этот код варианта??? RM и K&R вам в помощь... Не знаете как обратиться к слову, полуслову или байту??? Так тренируйтесь... а то в F0 или F3 ещё и нарвётесь на неприятность с данными в SPI... Или не знаете... что у BSRR приоритет установки перед сбросом??? В RM об этом чёрным по белому написано... Или не знаете... что операции с ODR неатомарны... а с BSRR - атомарны??? Вам лень читать??? А мне лень писать... такова жизнь...