|
|
  |
"Оптимизация" в WinAVR и как с этим бороться |
|
|
|
Nov 18 2011, 22:38
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(MaxiMuz @ Nov 18 2011, 18:06)  большая - маленькая зависит от типа МК. Привожу конкретный пример: Код http://www.radio.ru/archive/2010/07/a19.shtml программка писалась на асме под ATtiny2313, нехватило памяти программ под вторую таблицу мелодий для звонка. Если бы тогда писал на Си то вообще не хватилобы памяти на одну таблицу ! Это при том что я еще проводил оптимизацию программы в ассеблере  Поверьте старому С-шнику, избегающему по мере возможности написания программ на асме -- Вам показалось. Что Вы проводили оптимизацию этой ассемблерной программы. Бегло просмотрел программу из архива, даже толком не вникая в алгоритмы. Просто мелкая местная оптимизация — и несколько десятков байт как с куста. А если ещё вникнуть в алгоритмы, то и того более найдётся. Примеры: Код #ifndef OPTIMISED sbrc ModeR,BitModeAlrm rjmp Begin sbrc ModeR,BitModeWtch rjmp Begin #else;;; -2 sbrs ModeR,BitModeAlrm sbrc ModeR,BitModeWtch rjmp Begin #endif Там таких два места. На этой мелочи — уже 4 байта. Любой уважающий себя С-компилятор покраснел бы от стыда за такой код: Код ; Определение интервала 1 #ifndef OPTIMISED ; if ( MemdP <= Thr_AdP ) goto TCAdi11; else goto TCAdi12; cpi MemdPH,Thr_AdPH brcs TCAdi11 brne TCAdi12 cpi MemdPL,Thr_AdPL brcs TCAdi11 brne TCAdi12 #else;;; -4 bytes ; if MemdP >= (Thr_AdP + 1) ) goto TCAdi12 ldi temp, high( Thr_AdP + 1 ) cpi MemdPL, low( Thr_AdP + 1) cpc MemdPH, temp brcc TCAdi12 #endif Код #ifndef OPTIMISED ; if ( MemdP <= DeltaT ) goto TCA12; esle goto TCA11; cp MemdPH,DeltaTH brcs TCA12 brne TCA11 cp MemdPL,DeltaTL brcs TCA12 brne TCA11 #else ;;; -6 ; if ( DeltaT >= MemdP ) goto TCA11; cp DeltaTL,MemdPL cpc DeltaTH,MemdPH brcc TCA11 #endif И таких мест там не одно. Тут вообще что-то странное было. CODE ;_________ Запись таблицы регистрации нажатий в EEPROM ;* ;* CLI push temp2 push ZL push ZH #ifndef OPTIMISED clr ZL clr ZH b4: mov temp,ZL ldi temp2,low(Edata1) add temp,temp2 OUT EEAR,temp ldi temp2,low(data1) add ZL,temp2 LD temp,Z sub ZL,temp2 OUT EEDR,temp SBI EECR,EEMPE; SBI EECR,EEPE b5: sbic EECR,EEPE rjmp b5 cpi ZL,MaxCount*2-1 breq b6 ld temp,Z+ rjmp b4 b6: ldi temp,low(EMrk1) OUT EEAR,temp ldi temp,0xab OUT EEDR,temp SBI EECR,EEMPE; SBI EECR,EEPE
#else ;;; -14b
ldi ZL, low(data1) clr ZH ldi temp2, Edata1 b4: ld temp, Z+ rcall WrEE inc temp2 cpi temp2, MaxCount*2-1 brne b4 b6: ldi temp2,low(EMrk1) ldi temp,0xab rcall WrEE #endif
pop ZH pop ZL pop temp2 SEI
m9: CLR EnGen clr count m11: ;______ Задержка ldi temp,ZDelayL mov CL,temp rcall DelayTime Begin__: rjmp Begin
#ifdef OPTIMISED WrEE: OUT EEAR,temp2 OUT EEDR,tempЯ SBI EECR,EEMPE; SBI EECR,EEPE WrEEw: sbic EECR,EEPE rjmp WrEEw ret #endif Эта запись вообще дважды повторяется, в слегка разном окружении. Её всю в подпрограмму надо пустить. Ещё tiny2313 есть регистры GPIOR, аж три штуки. Все флаги успешно летят туда без увеличения объёма кода с незначителным увеличением времени. Освобождается два регистра, причём "верхних". Явно где-то поможет сократить код. Я бы постарался выделить регистровую пару Y под указатель на блок данных в DSEG, заменив все LDS/STS на LDD/STD. Ещё так на глаз минимум полсотни байт. Напоминаю — это всё не особо вникая в суть кода.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Nov 20 2011, 11:58
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Цитата(neiver @ Nov 18 2011, 20:23)  К сожалению, в avr-gcc LTO не работает. Компилятор к 5-ой аврстудии скомпилирован без поддержки LTO, я пытался скомпилировать avr-gcc сам со включеной LTO - не собрался, видимо чего-то в в AVR бекэнде не хватает. У меня к сожелению, не достаточно глубокие познания в тонкостях компиляторов и знания абревиатур, да и практики маловато. Хотел узнать , что такое LTO ? Цитата(ReAl @ Nov 19 2011, 01:38)  Поверьте старому С-шнику, избегающему по мере возможности написания программ на асме -- Вам показалось. Что Вы проводили оптимизацию этой ассемблерной программы. Бегло просмотрел программу из архива, даже толком не вникая в алгоритмы. Просто мелкая местная оптимизация — и несколько десятков байт как с куста. А если ещё вникнуть в алгоритмы, то и того более найдётся. Примеры: Код #ifndef OPTIMISED sbrc ModeR,BitModeAlrm rjmp Begin sbrc ModeR,BitModeWtch rjmp Begin #else;;; -2 sbrs ModeR,BitModeAlrm sbrc ModeR,BitModeWtch rjmp Begin #endif Там таких два места. На этой мелочи — уже 4 байта. Я уважаю людей которые хорошо разбираются в С-ях !  Да, действительно я не достаточно тщательно провел оптимизацию данной программы ! И в приведнном примере , я возможно проглядел, либо просто не стал обращать внимание на эти мелочи ... п.с. а про код "записи в ЕЕПРОМ" я прекрасно знал , просто в этой программе не стал его выделять в отдельную процедуру. Цитата(ReAl @ Nov 19 2011, 01:38)  Любой уважающий себя С-компилятор покраснел бы от стыда за такой код: Код ; Определение интервала 1 #ifndef OPTIMISED ; if ( MemdP <= Thr_AdP ) goto TCAdi11; else goto TCAdi12; cpi MemdPH,Thr_AdPH brcs TCAdi11 brne TCAdi12 cpi MemdPL,Thr_AdPL brcs TCAdi11 brne TCAdi12 #else;;; -4 bytes ; if MemdP >= (Thr_AdP + 1) ) goto TCAdi12 ldi temp, high( Thr_AdP + 1 ) cpi MemdPL, low( Thr_AdP + 1) cpc MemdPH, temp brcc TCAdi12 #endif Код #ifndef OPTIMISED ; if ( MemdP <= DeltaT ) goto TCA12; esle goto TCA11; cp MemdPH,DeltaTH brcs TCA12 brne TCA11 cp MemdPL,DeltaTL brcs TCA12 brne TCA11 #else ;;; -6 ; if ( DeltaT >= MemdP ) goto TCA11; cp DeltaTL,MemdPL cpc DeltaTH,MemdPH brcc TCA11 #endif И таких мест там не одно. Суть правильная, только ваш оптимизатор почемуто в метках переходов ошибся ! при условии С=0 должна выполняться какраз противоположная ветвь ! Вот так доверяй потом оптимизаторам !! И в данном куске, почему я сначала старшие байты решил проверять : хотел ускорить алгоритм проверки. Хотя сейчас понимаю что это не к чему было. И всеже вернусь к использованию регистровых глобальных перемнных. В теме: заголовок"Второй раз повторяю, GCC не поддерживает volatile register переменные. Не поддерживает - это значит что нет гарантии что любой код использующий volatile register переменные будет работать при любом уровне оптимизации. К сожалению варинг сообщающий об этом пропал где-то в 2005 году и если я правильно понял его вернули в феврале 2008. Попробуйте откомпилировать тестовый пример с "volatile register" GCC 4.4 с клюем -Wall. Или с ключом -Wvolatile-register-var на 4.3. Анатолий. " были упомянуты ключики. Я не нашел в дока назначение этих ключей, плохо ориентируюсь описании WinAVR. Есть ли возможность отключить в опциях использование компилятором конкретных регистров ?
Сообщение отредактировал MaxiMuz - Nov 20 2011, 11:15
|
|
|
|
|
Nov 20 2011, 12:23
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(MaxiMuz @ Nov 20 2011, 12:55)  Хотел узнать , что такое LTO ? Link-Time-Optimiser Зачатки его есть и в нынешнем линкере, ключ --relax. Это, например, превращение линкером длинного вызова в короткий. На этапе компиляции ещё неизвестно, насколько близко лягут куски из разных файлов и на больших кристаллах компилятром используется только длинный. LTO делает полную оптимизацию, зная уже всё о линкуемой программе (включая устройство библиотечных функций, если они были скомпилировані с соо-тветствующим ключом). Цитата(MaxiMuz @ Nov 20 2011, 12:55)  Да, действительно я не достаточно тщательно провел оптимизацию данной программы ! И в приведнном примере , я возможно проглядел, либо просто не стал обращать внимание на эти мелочи ... Ну там дальше и не такие мелочи на cp/cpc набегают, как тут на пропусках. Цитата(MaxiMuz @ Nov 20 2011, 12:55)  п.с. а про код "записи в ЕЕПРОМ" я прекрасно знал , просто в этой программе не стал его выделять в отдельную процедуру. И какой смысл после этого было говорить, что "на ассемблере влезла одна мелодия и то после оптимизации, а С и одна не влезла бы" ? ;-) У С по длине выигрывает не любая ассемблерная реализация — только потому, что она ассемблерная  — а только действительно вылизанная. Прикидочно, оценивая возможное ужатие Вашего кода байт на 150-200, — тут и С-шная реализация должна легко влезть. Цитата(MaxiMuz @ Nov 20 2011, 13:58)  Суть правильная, только ваш оптимизатор почемуто в метках переходов ошибся ! при условии С=0 должна выполняться какраз противоположная ветвь ! Ну там в одном из мест я для нужной нестрогости сравнивания поменял порядок операндов, мог забыть про смену полярности перехода. Это не важно, ошибки в коде любой длины бывают. Главное то, что код можно написать короче. Там ещё ошибки есть, например, в одном из мест надо так: Код cpi temp2, Edata1 + MaxCount*2-1 Это до меня уже в субботу утром, в метро по дороге на работу в дрёме дошло. Отмазки: 1) Ну дак в пол второго ночи и наспех (вставать-то в шесть), да копипастом-редактированием (из головы я бы сразу правильно писал) и не такого наворотить можно 2) А С-компилятор и не ошибся бы с "полярностью" перехода. Цитата(MaxiMuz @ Nov 20 2011, 13:58)  Есть ли возможность отключить в опциях использование компилятором конкретных регистров ? -ffixed-reg, например -ffixed-reg=r2 Только нужно быть уверенным, что библиотека тоже этот регистр не использует.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Nov 23 2011, 11:30
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Цитата(ReAl @ Nov 20 2011, 15:23)  ........................................ -ffixed-reg, например -ffixed-reg=r2 Только нужно быть уверенным, что библиотека тоже этот регистр не использует. После вставики в Makefile Код CFLAGS += -ffixed-reg=r16 CFLAGS += -ffixed-reg=r18 Компилятор выдал ошибку: Код cc1.exe: warning: unknown register name: reg=r16 cc1.exe: warning: unknown register name: reg=r18 Что я не так делаю ? И где можно найти описание всех ключей компилятора GCC
|
|
|
|
|
Nov 23 2011, 21:19
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Тьху, ну конечно, -ffixed- regЧто-то меня на = потянуло :-) Цитата(demiurg_spb @ Nov 23 2011, 15:39)  Как всегда в гугле, забив в строке поиска: gcc optimization options Если бы гугл умел посылать пользователя на его локальный диск в file:///C:/WinAVR/doc/gcc/HTML/gcc-4.3.2/gcc/index.html в частности, file:///C:/WinAVR/doc/gcc/HTML/gcc-4.3.2/gcc/AVR-options.html file:///C:/WinAVR/doc/gcc/HTML/gcc-4.3.2/gcc/Code-Gen-Options.html а также file:///C:/WinAVR/doc/avr-libc/avr-libc-user-manual/index.html так он ещё бы трафик пользователю сэкономил. ___________________________ — А я так XP-шку до конца и не прошёл... — Так это же не игрушка, а ОС — Откуда инфа 8-O ???
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Nov 25 2011, 08:40
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Спасибо за ответы ! Опять я возвращаюсь к своим любимым регистровым глобальным переменным. Вначале программы обычно происходит инициализация переменных. В моей программе три регистровых глобальных переменных. Одна из них обьявленна как битовая область, и с ней проблем нет. Две других как беззнаковый байт (см. программу). В месте задания им начальных значений, компилятор игнорировал эти операции, чтобы я не делал. Все уровни оптимизации , кроме -O0 исключали эти команды. Но, после перемещения блока инициализации в отдельную подпрограмму , компилятор всеже их заметил, и правильно откомпилировал! У меня возникла мысль, что какието ключи не позволяют оптимизатору игнорировать команды в подпрограммах. Хотел бы услышать ваши предположения. Код #include <avr/io.h> #include <avr/interrupt.h> // задает макросы sei() , cli() #include <inttypes.h>
//===================================================================== // определение регистров: volatile register uint8_t FLCnt asm("r16"); //фоновый счетчик длительности переключения свдиода volatile register uint8_t DBKCnt asm("r18"); //фоновый счетчик для отсчета периода блокировки опроса кнопок volatile register struct { uint8_t bDbRKey : 1; uint8_t bFLed : 1; } RFGP asm ("r17");
//;---------------------------------------------------------------------------------------------------------------------------------------- //=== Определение макросов #define sbi(p,b) (p |= (1<<b)) //Установить бит //;---------------------------------------------------------------------------------------------------------------------------------------- //Определение портов: /* направление для порта В*/ #define DIRB 0b00010001 /* Pull-ups для порта В*/ #define PUPB 0b00000101
//;---------------------------------------------------------------------------------------------------------------------------------------- //Определение контактов #define Control1 PB4 /* линия управления VT (1 - откр; 0 - закр ) */ #define Btn1 PB2 /* линия кнопки (на общ.пров) */ #define Led PB0 /* линия светодиода ("1" - вкл. ч/з резистор на общ.) */
//;---------------------------------------------------------------------------------------------------------------------------------------- /* длительность запрета на опрос клавиш t=(101-1)*0,1=10 sec */ #define Vl_DBKCnt 101
/* t=5*0,1=0,5 sec */ #define Vl_FLCnt 5
// Маска кнопок:"Btn" #define KeyMask (1<<Btn1)|(1<<1)
//===================================================================== //===================================================================== SIGNAL ( SIG_OUTPUT_COMPARE0A) {//______ Моргание светодиода t=5*0,1=0,5 sec if (!(--FLCnt)) {//__ Переключение свдиода FLCnt=Vl_FLCnt;// перезагрузка ф.счетчика !!!! ~(RFGP.bFLed); sbi (PINB,Led);// ________ Переключение свдиода ! }
//______ Формирование запрета на опрос клавиш t=(101-1)*0,1=10 sec if (RFGP.bDbRKey==1) { //__ проверка на первый запуск if (DBKCnt == 0 ) DBKCnt=Vl_DBKCnt; --DBKCnt;//__ продолжение счета if (DBKCnt==0) // checking of =0 { //__ сброс флага RFGP.bDbRKey=0; DBKCnt=Vl_DBKCnt; } } }
//============================================================================= //============================================================================= int main (void) { //_________________________ ИНИЦИАЛИЗАЦИЯ _____________________________ uint8_t temp;
PORTB=PUPB; //иницализация порта B DDRB=DIRB; // задание направления для порта B TIMSK0=(1<<OCIE0A); /* установка разр. прер-ия по совпадению т/сч.0 с регистром OCR0A */ OCR0A=234; //загрузка регистра совпадения OCR0A коэф. деления TCCR0A= (1<<WGM01); //установка режима СТС - обнуление Т/С0 при совпадении с регистром OCR0A TCCR0B=(1<<CS02)|(1<<CS00); // <---- конфигурация и запуск сч-ка в реж. СТС с предделителем ckl/1024 RFGP.bDbRKey=0; RFGP.bFLed=0; [color="#ff0000"]DBKCnt=Vl_DBKCnt; //задание начальных значений для счетчиков FLCnt=(uint8_t)Vl_FLCnt;[/color]
sei (); // Разрешение общего прерывания //____________________ ЦИКЛ РАБОЧЕЙ ПРОГРАММЫ ______________________ while (1) { //____ Считывание кнопки temp=~(PINB); temp &= KeyMask; if (temp) { //______ Обработка нажатия кн."Btn" if ( RFGP.bDbRKey==0)//____ проверка на запрет считывания кнопок { RFGP.bDbRKey=1; // sbr RFGP,(1<<bDbRKey) sbi (PINB,Control1); // ________ Переключение линии Control1 _______________!!!!! } } } }
|
|
|
|
|
Nov 25 2011, 12:07
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Цитата(Сергей Борщ @ Nov 25 2011, 12:27)  Прочитайте еще раз про яйцо и микроволновку. Мне лень считать, сколько раз вам писали, что регистровую переменную нельзя делать volatile, о чем прямым текстом пишут создатели компилятора. Вы же с упорством пьяного продолжаете писать register volatile и пенять на компилятор. Извиняюсь что возмутил вас употребением и применением volatile ! Кстати сказать, из ответ на вопрос о использовании Volatile к регистровым глобальным переменным я со своим плохим знанием английского понял что компилятор уже считает регистровые переменные как volatile. Другими словами использование квалификатора бессмысленно. Я убрал в обьявлении переменных FLCnt, DBKCnt volatile, результат тотже ! Вопрос остается открытым
|
|
|
|
|
Nov 25 2011, 14:35
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(AHTOXA @ Nov 25 2011, 16:16)  Какой? Почему регистровые переменную, вне зависимости от наличя слова volatile, компилятор, предупредивший о своём отношении к таким объявлениям, не рассматривает как volatile и выкидывает из main() их инициализацию, так как в этой же main() к переменным больше обращений нет, а про обращение к ним из прерываний компилятор «не знает».
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Nov 25 2011, 20:32
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Провёл маленький эксперимент. Убрал все эти register... asm() Начало файла стало выглядеть так: Код //===================================================================== // определение регистров: volatile uint8_t FLCnt; volatile uint8_t DBKCnt;
#define DbRKey 0x01 #define FLed 0x02 Обработчик прерывания так: Код SIGNAL(SIG_OUTPUT_COMPARE0A) { uint8_t temp;
temp = FLCnt; if (!(--temp)) { temp = Vl_FLCnt; // ~(RFGP.bFLed); Эта штука ничего осмысленного не делает. // и в выходном коде от неё ничего И НЕ БЫЛО PINB |= _BV(Led); } FLCnt = temp;
if ( GPIOR0 & DbRKey ) { temp = DBKCnt; // на мой взгляд странные манипуляции, но оставил как есть if (temp == 0) temp = Vl_DBKCnt; --temp; if (temp == 0){ GPIOR0 &= ~DbRKey; temp = Vl_DBKCnt; } DBKCnt = temp; } } При компиляции для atmega168 количество команд в обработчиках прерывания для старого и нового варианта кода сохранилось. Все push/pop в начале и в конце обработчика совпадают, общий ход практически тот же. Просто несколько команд mov заменилось на lds, sts. В итоге длина в байтах для нового варианта увеличилась на 8 байт и стала 66 байт. Время выполнения увеличилось на 4 такта. Только время тут «абсолютно», а вот освобождение трёх зафиксированных до этого регистров освободит руки компилятору в других местах и общая длина кода всей программы ещё и уменьшиться может. За что боролись? Думаю, сначала нужно научиться просто на С писать, а потом про все эти зафиксированные регистры думать.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Nov 28 2011, 10:59
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
AHTOXA с вами не поспоришь ReAl По сути , кусок из вашего варианта : Код temp = FLCnt; if (!(--temp)) { подобен коду сформированному с использованием регистровых переменных: Код if (!(--FLCnt)) 2e: 80 2f mov r24, r16 30: 81 50 subi r24, 0x01; 1 32: 08 2f mov r16, r24 34: 11 f4 brne .+4 Но даже если закрыть глаза на бессмысленные mov rd,rs , как вы сами признали чуть быстрее и меньше по коду. И в использование возможностей железа по максимуму, даже учитывая корявость компилятора, нет ничего предосудительного  А абстрагирование от железа приводит к появлению таких опусов как Win7 ! с неоправданно раздутым кодом и тормозным ядром ! А если используется мощн.математический аппарат, то как я себе это представляю, здесь полезно оценить что важнее, быстый доступ к регистровым переменным или быстрое выполнение мат.операций. Код if ( GPIOR0 & DbRKey ) кстати в ATtiny13 (под него я пишу код) нет таких регистров вв./выв. которые можно так использовать! Цитата(ReAl @ Nov 25 2011, 23:32)  .......................... Думаю, сначала нужно научиться просто на С писать, а потом про все эти зафиксированные регистры думать. с вами полностью согласен , но гибкое использование железа тоже часть "знания Си"
Сообщение отредактировал MaxiMuz - Nov 28 2011, 10:56
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|