|
Сопоставление проектов С и АСМ, Немного цифр |
|
|
|
 |
Ответов
(1 - 62)
|
Feb 18 2008, 07:58
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Для меня лично есть одно НО: вот я использую мультипоточное программирование на асме, стек-фреймы и прочее - так проще и читабельнее выглядят программы. Диспетчеризация кооперативная. При переходе от потока к потоку в свопинге участвуют только содержимое PC и SP. Все крутится на минимальном наборе регистров, потому что большая их часть задействована в прерываниях, в которых тоже свопинга мало. И под такую Homebrew - ось я заточить "С" не смогу никак. Вот вчера запустил ногодрыгалку на меге 640 по теме, которой я к Вам в личку напрашивался. Из 15 возможных ШИМ используются все с частотой до 10кГц. Из 16 АЦП используются все. + Дисплей 7 сегментный + клава + 2 модбаса + 1 RS232. И рад бы я это на "С" написать, потому как времени на это немного, но прикинул, что с учетом неперенесенных наработок и определенной критической массы Time-critical sections, да еще и трудностей с мультипотоком, писанины будет раза в 3 больше.
А тут же параллельно силовая поделка на: пульт на меге 8 (+модбас) контроллер: мега48+PIC18f2431 общаются по I2C (+модбас) Здесь без "С" вообще никуда.
А по качеству кода у меня и к Winavr претензий нет. И что самое приятное - чем тупее/тривиальнее/читабельнее код, тем лучше оптимизация.
|
|
|
|
|
Feb 18 2008, 08:32
|

Профессионал
    
Группа: Модераторы
Сообщений: 1 951
Регистрация: 27-08-04
Из: Санкт-Петербург
Пользователь №: 555

|
Это все не показатель. Если код на С заточить например под IARовский компилер и использовать его фичи: #pragma inline=forced __z __z_x и т.д. посмотреть как компилятся определенные конструкции у меня например есть макросы работы с кольцевым буфером в 256 байт выравненым, которые компилятся в Код LDS ZL, Head LDI ZH, HIGH(Buf) LD r??, Z+ STS Head, ZL
или если несколько байт надо вытащить
LDS ZL, Head LDI ZH, HIGH(Buf) LD r??, Z+ LDI ZH, HIGH(Buf) LD r??, Z+ STS Head, ZL использовать временные перменные Код { uint8_t tmp; if (tmp = TIFR1 & (1<<OCF1A)) { TIFR = tmp; .... } } можно получить код не хуже чем на асме! зато с большими функциями в которых используется много указателей и вычислений гемора не будет (IAR прекрасно пересылает указатели movw ZL:ZH, .....)
|
|
|
|
|
Feb 18 2008, 18:33
|
Местный
  
Группа: Свой
Сообщений: 408
Регистрация: 21-10-06
Из: Санкт-Петербург
Пользователь №: 21 527

|
Цитата(SasaVitebsk @ Feb 18 2008, 18:45)  Switec-овский алгоритм осилил за час Извиняюсь за оффтоп, но что такое "Switec-овский алгоритм"?
|
|
|
|
|
Feb 18 2008, 23:25
|

Местный
  
Группа: Свой
Сообщений: 479
Регистрация: 8-05-07
Из: г. Ставрополь. Северный Кавказ. Россия
Пользователь №: 27 606

|
ИМХО. Написать С код соизмеримый с асмом можно, но требует таких же трудозатрат. А вот интеретно, использовали ли Вы такие приёмы? Код unsigned char a; a = delay_key; //токмо для "оптимизации". Иар почему-то все переменные считает как volatile. if (KEY_ON()) { if ( a != 255 ) delay_key = a+1; } else if (a) delay_key = a-1
|
|
|
|
|
Feb 19 2008, 06:50
|

Йа моск ;)
     
Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610

|
Цитата А вот интеретно, использовали ли Вы такие приёмы? Дык это совершенно правильный код для помогания компилятору. В общем случае структура любой функции для камня с большим количеством регистров должна стремиться к такому виду: Код foo() { regvar1=memvar1; regvar2=memvar2; .... regvarn=memvarn; .... собственно вычисления .... memvar1=regvar1; memvar2=regvar2; ... memvarn=regvarn; } Другое дело, что сам компилятор должен стремиться к этому. Но не грех и помогать ручками. Конечно, пример вырожденный, но, я думаю, мысль мою описывает правильно...
--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
|
|
|
|
|
Feb 19 2008, 06:54
|
Гуру
     
Группа: Свой
Сообщений: 2 702
Регистрация: 14-07-06
Пользователь №: 18 823

|
Цитата(_Pasha @ Feb 18 2008, 11:58)  Для меня лично есть одно НО: вот я использую мультипоточное программирование на асме, стек-фреймы и прочее - так проще и читабельнее выглядят программы. ... А что такое стек-фреймы? Меня первый же безусловный переход вводит в ступор, а второй - в ярость. Это я так, нисколько не собираясь споров затевать.
--------------------
Уходя, оставьте свет...
|
|
|
|
|
Feb 19 2008, 07:59
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(Dog Pawlowa @ Feb 19 2008, 09:54)  А что такое стек-фреймы? Меня первый же безусловный переход вводит в ступор, а второй - в ярость. Это я так, нисколько не собираясь споров затевать. Ну, я сразу все не напишу. Модель вкратце, остальное по уточняющим вопросам. Код .dseg Entry_Point: .byte 2 Save_SP: .byte 2 Local_SP: .byte 2 Local_Stack: .byte my_Stack_Size
Var1: .byte 1 ;.............................. VarN: .byte 2
.set Local_Var_Org = Var1 Это по данным. Предположим, у нас уже Y адресует начало этого блока данных Тогда пролог процедуры: Код ld zL,Y+ ld zh,Y+; load entry point in r0,spl in r1,sph st Y+,r0; store stack pointer st Y+,r1
ld r0,Y+ ld r1,Y+ cli out spL,r0 out sph,r1 sei ; at this line Y points to LOCAL DATA ijmp; resume procedure execution Эпилог соответственно восстанавливает все обратно Очень полезный макрос Код .macro leave; <new_entry_point_addr> ldi zL,low(@0) ldi zh,high(@0) jmp Epilogue .endm Итого в нашу пользу: 1. Имеем реентерабельные процедуры. Могу выложить пример, где 4 одинаковых процесса крутятся таким образом, выдают инфу каждый на свой индикатор (7-сегм), обрабатывают энкодер как орган управления (правда, плохонько, но претензий нет, я и не работаю над собой) 2. Читабельность зашкаливает Вместо приснопамятных переменных State мы переопределяем точку входа в процедуру. Что-нить проверили, кое-какое условие, затем просто пишем leave addr вместо rjmp Код Label1: call Test_Some_Conditions; breq Label2; leave Label1; exits here, but next entry point will be Label1 Label2:; continue program execution 3. Если глубина локального стека позволяет, можно вызывать другие подпрограммы, и если в них использовать leave, все еще интереснее. Т.е. с точки зрения программиста нормальная логика выполнения программ не нарушается. 4. Локальные данные адресуются с помощью yL:yH, намертво закрепленных за указателем Продолжать ?
|
|
|
|
|
Feb 19 2008, 10:42
|
Гуру
     
Группа: Свой
Сообщений: 2 702
Регистрация: 14-07-06
Пользователь №: 18 823

|
Цитата(_Pasha @ Feb 19 2008, 11:59)  Продолжать ? Нет, спасибо. Интересно, конечно, но я уже перешел на 'C' и возвращаться не имеет смысла. Однажды видел проект (система продажи билетов на ЖД эпохи начала перестройки), в котором использование макросов и условной компиляции поражало воображение, но для этого нужна свежая молодая голова. Too late
--------------------
Уходя, оставьте свет...
|
|
|
|
|
Feb 19 2008, 10:57
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(Dog Pawlowa @ Feb 19 2008, 13:42)  для этого нужна свежая молодая голова. Too late  Извиняюсь, не могу смолчать. 1. У меня голова уже точно не молодая и не свежая - сын в институте... 2. У атмелов-то правильных макросредств только недавно появилось  3. Немножко все-таки продолжу про минусы того,чего написал выше. Главный минус - это как детский вопрос : "Можно ли прикрутить туда 1-wire ?"
|
|
|
|
|
Feb 19 2008, 11:42
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(Т.Достоевский @ Feb 19 2008, 03:25)  ИМХО. Написать С код соизмеримый с асмом можно, но требует таких же трудозатрат. А вы анализировали глобально производительность программы в процессе оптимизации? Существует теория оптимизации (естественно). Если исключить оптимизацию по коду (в большенстве случаев удобнее просто другой чип взять), а брать оптимизацию по скорости, то возникает следующая ситуация. Оптимизировать требуется только незначительную часть кода. Обычно - единицы процентов. Это даёт существенный результат. Оптимизация же остальной части сказывается не сильно. Опыт показывает, что оптимизировать этот кусочек лучше именно в виде "подсказок" для компилятора. У меня это дало практически идеальный код. потери в сравнении с асмом - 5% не больше. К сожалению, такая оптимизация тоже получается (CPU-зависима). Выработав некоторые общие принципы, можно сразу писать так, что будет код очень эффективный. Вот пример Код void Point(uint8_t X,uint8_t Y,uint8_t Color) // Вывести точку { uint8_t maska; // Смещение относительно байта uint8_t *addr; // Адрес экраного байта
maska =0x3; if((X & 2)==0) { maska = 0x30; Color <<= 4; } if((X & 1)==0) { maska <<=2; Color <<=2; } maska = ~maska; if(NEkr>0) addr = &Ekr[SIZE_EKR]; else addr = Ekr;
addr += (Y*SizeXb)+(X>>2); maska &= *addr; maska += Color; *addr = maska; } /*offset = (X & 3) << 1; // выделить смещение точки в байте maska = ~(3<<offset); addr = (Y*SizeXb)+(X>>2); maska &= Ekr[NEkr][addr]; maska += Color << offset; Ekr[NEkr][addr] = maska; }*/ Результат - двукратный рост производительности.
|
|
|
|
|
Feb 19 2008, 13:13
|
Местный
  
Группа: Участник
Сообщений: 256
Регистрация: 6-03-05
Из: Екатеринбург
Пользователь №: 3 112

|
Цитата(SasaVitebsk @ Feb 19 2008, 16:42)  Результат - двукратный рост производительности. И можно ещё быстрее и компактней...
|
|
|
|
|
Feb 19 2008, 17:00
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(forever failure @ Feb 19 2008, 17:13)  И можно ещё быстрее и компактней... Наверняка. В данном примере просто наглядно видно что я вместо вызова п/п сдвига на N бит делаю анализ указателя сдвига с генерацией констант. Табличный метод в данном месте хуже. вот кусочек результирующего листинга Код 891 if(chBright<0) \ 00000004 2322 TST R18 \ 00000006 F422 BRPL ??PointB_0 892 { 893 ColorX =0; \ 00000008 E030 LDI R19, 0 894 ch = -chBright; \ 0000000A 2F02 MOV R16, R18 \ 0000000C 9501 NEG R16 \ 0000000E C002 RJMP ??PointB_1 895 } 896 else 897 { 898 ColorX =3; \ ??PointB_0: \ 00000010 E033 LDI R19, 3 899 ch = chBright; \ 00000012 2F02 MOV R16, R18 900 } 901 maska =0x3; \ ??PointB_1: \ 00000014 E023 LDI R18, 3 902 if((X & 2)==0) \ 00000016 FB41 BST R20, 1 \ 00000018 F02E BRTS ??PointB_2 903 { 904 maska = 0x30; \ 0000001A E320 LDI R18, 48 905 ColorX <<= 4; \ 0000001C 9532 SWAP R19 \ 0000001E 7F30 ANDI R19, 0xF0 906 ch <<= 4; \ 00000020 9502 SWAP R16 \ 00000022 7F00 ANDI R16, 0xF0 907 } Как видим вполне прилично. На асме у меня получился выигрыш в несколько тактов.
|
|
|
|
|
Feb 20 2008, 08:15
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Теперь немного о другом. Рискую утомить читателя, поэтому-кратко. 0. Код под Winavr 1. В EEPROM имеется несколько наборов параметров, активный набор обозначен селектором: Код uint8_t EEMEM Selector=0x00;
#define Max_Applicable_Paramsets 4 typedef struct { uint16_t Cap_Chg_Time; // ==0 then continuous mode uint16_t Cap_Charge_Volt; // chg to volt uint16_t Cap_Discharge_Volt; // disch downto this value (volts) uint16_t Operating_Current; // ==0 then voltage mode uint8_t Amplitude; // used for voltage mode boost uint16_t PWMfreq; uint16_t Expose_Time; // in 10ms step uint8_t Rly_Mode; // ==0 none rly is turned on at selection
} App_Param_Record; // eeprom implementation App_Param_Record EEMEM Parameter_set[Max_Applicable_Paramsets] = {{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}}; 2. В памяти программ есть дескрипторы этих параметров, но только для активной страницы Код //дескриптор параметра typedef struct { uint8_t xcmd; // параметр соответствует некоторой команде, получаемой извне void *ojaddr; // адрес параметра uint8_t ojsize;// размер данных+ здесь же добавил опции размещения (см ниже) этих данных } Cm_Xref_;
typedef Cm_Xref_ PROGMEM Cm_Xref_t; // объявили // опции размещения данных #define Fitter_mask 0xC0 // размещено во флеше (т.е. данные будут только для чтения) #define Flash_Fit 0x80 // размещено ЕЕ #define EE_Fit 0xC0 // размещено в ОЗУ и только для чтения #define Sram_Fit_RO 0x40 //fitted in sram and read only
// Размещено в буфере типа App_Param_Record, в который будет копироваться страница параметров #define Relative_Fit 0x00 // relative to struct
// Команды на чтение 0x00..0x7F на запись 0x80..0xFF #define Inhibit_read_write_mask 0x7F
//собсно описания
static App_Param_Record Edited_Record; // sram shadow
#define Rel_Param(member) &Edited_Record.member,Relative_Fit+sizeof(Edited_Record.member)
static Cm_Xref_t Cm_Xref[Max_cmd+1]= { {cm_Brief,0,0}, // version crc {cm_NetLocate,&Net_ID,EE_Fit+1}, {cm_Set_Selector,&Selector,EE_Fit+1}, {cm_Charge_Time,Rel_Param(Cap_Chg_Time)}, {cm_Charge_Volt,Rel_Param(Cap_Charge_Volt)}, {cm_Discharge_Volt,Rel_Param(Cap_Discharge_Volt)}, {cm_Operating_Current,Rel_Param(Operating_Current)}, {cm_Amplitude,Rel_Param(Amplitude) }, {cm_Expose_Time,Rel_Param(Expose_Time)}, {cm_Rly_Mode,Rel_Param(Rly_Mode)} }; Сложняк ? Да. Для описания параметров из активной страницы пришлось принять, что эту страницу мы будем копировать в буфер. По-другому никак. Потому что, иначе мы разрушаем переносимость программы. 3. Проглотил я, короче, эти сложности - в конце концов памяти программ я пока не потерял. В общем, дело доходит до реализации. Обрезал я поиск команды. Оставил обращение типа Cm_Xref[command]. Привожу процедуру без купюр и особых комментов. Все названия сторонних функций - сами за себя говорят. Код void Track_Pult(void) { uint8_t buffer[8],Rsz,*addr,k; Packet_Layout *pack; Cm_Xref_ xref,*xr;
Rsz = RS485_pack_ready(); if (!Rsz) return; // <datasize> <receiver> <sender> <command> <data> <crc> pack = &RS_buff;
if((pack->receiver) != Get_Net_ID()) goto Catch;
// else -continue parsing received pack Sender_Addr = pack->sender;// store sender Rsz -= 5;
k = pack->command & Inhibit_read_write_mask;
if (k == cm_Remote_Start){ Set_Start_Status(); Send_error(er_OK); // :) no errors goto Catch; }
if (k == cm_Remote_Stop) { Set_Stop_Status(); Send_error(er_OK); goto Catch; }
if (k >= Max_cmd) {Send_error(er_Unknown_cmd);goto Catch;}
xr = memcpy_P(&xref,&(Cm_Xref[k]),sizeof(xref));
uint8_t options,objsize; void *compaddr;
options = xref.ojsize; objsize = options & (~Fitter_mask); options &= Fitter_mask; addr = xref.ojaddr; k = pack->command != k; if (Dev_Status.IsWorking) {Send_error(er_Busy); goto Catch;} if (Rsz != objsize) {Send_error(er_InvParam); goto Catch;}
switch (options){ case Flash_Fit: if (k) Send_error(er_Read_Only); else Send_data(er_OK,memcpy_P(&buffer,addr,objsize),objsize); break;
case EE_Fit: eeprom_read_block(&buffer,addr,objsize); if (k) {eeprom_write_block(addr,&(pack->data),objsize);Send_error(er_OK);} else Send_data(er_OK,&buffer,objsize); break; case Relative_Fit: compaddr = Paramset(); eeprom_read_block(&Edited_Record,compaddr,sizeof(Edited_Record)); if (k) {eeprom_write_block(&Edited_Record,compaddr,sizeof(Edited_Record)); Send_error(er_OK);} else Send_data(er_OK, addr,objsize); break;
} Catch: RS485_purge_received(); return; } Утомил? Могу сказать одно - на асме эта байда была бы существенно проще. Я уже не говорю про смысл записи в EEPROM значений, которые не изменились.  Если я еще чего туда наверну, мне памяти точно не хватит. Если все-же кто-то "асилил" то, что я накрапал, подскажите, как бороться со сложняком в системе описания параметров. Через "C", ессно. З.Ы. О цифрах забыл размер кода 364 байт. Время - пофиг. Оптимизация -Os З.З.Ы. Подчистил грязь - исправил "caller" на "receiver"
|
|
|
|
|
Feb 20 2008, 16:25
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Не могу сказать, что я точно понял смысл, но попробую. Хотя если из другой оперы - то извиняй. (Мне кажется я делал что-то подобное) 1. Все команды строю однотипно типа так. Код struct AddrKomXx { uint8_t Name; // Имя команды uint16_t TimeStart; // Время начала исполнения команды } *KomXx;
struct AddrKomC { uint8_t Name; // Имя команды uint16_t TimeStart; // Время начала исполнения команды uint8_t TimeMashtabTek, // Текущее значение масштаба для времени исполнения TimeMashtab; // Масштаб для времени исполнения uint16_t TimeLife; // Время исполнения команды с учётом масштаба int16_t BegX,BegY, // Начало объекта (X,Y) SizeX,SizeY; // Размеры объекта (X,Y) int8_t VecX,VecY; // вектор перемещения объекта (X,Y) uint8_t color; // цвет заливки } *KomC;
struct AddrKomK { uint8_t Name; // Имя команды uint16_t TimeStart; // Время начала исполнения команды uint8_t KernTek; // Текущее значение масштаба для времени исполнения } *KomK; У меня их море, это кусочек. Причём первая AddrKomXx это несуществующая "виртуальная", для определения текущей. Определение делаю так. Код KomXx = (struct AddrKomXx*) AdrActiveKom[i]; // Прочитать адрес текущей активной команды if(KomXx->TimeStart>Status.TekTime) continue; // Если не подошло время для исполнения команды, то пропустить данную команду switch (KomXx->Name) { /* Команда "END" - "Ролик завершить" */ case 'S': /* Команда "Stop-Kadr" - "Остановить модификацию картинки" */ KomS = (struct AddrKomS*) AdrActiveKom[i]; // Прочитать адрес текущей активной команды ... То есть сначала адрес начала команды "сопоставляю" с "виртуальной" командой все поля в которой в точности совпадают во всех командах и по ней осуществляю контроль команды, а потом "пересопоставляю" с уже реальной командой, где уже реальные поля и работаю с ней. Если работаешь с сылками(адресами), как я, то без разницы где находится команда и её можно не пересылать вообще. Имей один буфер приёма - с ним и работай. Я например даже удаляю отработавшие и делаю "сборку мусора". zltigo ещё интереснее сделал, если я подосвобожусь тоже так попробую - он свой диспетчер кучи написал и работает с занятием/освобождением весьма грамотно. У меня, принципиально, возможны проколы в данном месте - я это знаю.
|
|
|
|
|
Feb 20 2008, 18:49
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(defunct @ Feb 20 2008, 17:51)  Стиль значит асмовый используется.. Попробуйте писАть на С, а не на asm сишными словами. Одинаковые значения в eeprom тоже никто не заставляет писать, в функции записи байта сделать вычитку ячейки, совпадает с тем что пишем - выход. 1. Дописываю тот же кусок кода на асме. Для сравнения. Получается в полновесном варианте, со всеми обработками ошибок, с поиском правильной команды и с таблицей - меньше 200 байт. Завтра выложу в аттаче, дабы не превращать тему в "кодовый флуд". По поводу "писАть на С" - я "С" терпеть не могу еще с 1992 года  Проблемы-то начинаются с обращения к памяти программ за данными. Этот процесс нигде не автоматизирован, отсюда сложности. 2. Переопределять стандартные функции НЕ ХОЧУ. Цитата(SasaVitebsk @ Feb 20 2008, 19:25)  Не могу сказать, что я точно понял смысл, но попробую. Хотя если из другой оперы - то извиняй. (Мне кажется я делал что-то подобное) Смысл: Хранение в девайсе настроечных параметров и обращение к ним, например, через команды по последовательному порту. Цитата ... он свой диспетчер кучи написал и работает с занятием/освобождением весьма грамотно. У меня, принципиально, возможны проколы в данном месте - я это знаю. Вот! Там, где дело касается ОЗУ - ессно "С" выиграет.
|
|
|
|
|
Feb 20 2008, 21:06
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(_Pasha @ Feb 20 2008, 22:49)  Проблемы-то начинаются с обращения к памяти программ за данными. Этот процесс нигде не автоматизирован, отсюда сложности. 2. Переопределять стандартные функции НЕ ХОЧУ. Смысл: Хранение в девайсе настроечных параметров и обращение к ним, например, через команды по последовательному порту. Вот! Там, где дело касается ОЗУ - ессно "С" выиграет. Никаких проблем при обращении к памяти программ за данными я не заметил. Обращаешься так же как и к обычной переменной. Более того код получается очень эффективным. Пересылать абсолютно ничего не надо. По крайней мере в IAR. В редких случаях, когда идут разнотипные данные компилятору приходится явно указывать что идёт обращение к flash. Например Код // Знакогенераторы const void __flash *symbol[MAX_FONTS] = {&fnt0_6x8,&fnt1_8x8,&fnt2_10x10,&fnt3_11x13,&fnt4_9x14,&fnt5_16x16,&fnt6_16x16,&fnt7_12x13,&fnt8_16x15,&fnt9_14x16}; .... uint8_t __flash *addr; ..... addr = ((uint8_t __flash *)symbol[tekfonts])+(Znak*Fonts[tekfonts][1]); ..... А обычно и этого делать не приходится Код // структура данных по символам struct CharZnak { uint16_t sm; // Смещение от начала фонта uint8_t width; // Ширина символа };
//extern uint16_t __checksum; // CHECKSUM; //const __flash uint16_t EndFlashAdr @0xe5 = & __checksum;
extern const uint8_t __flash Fonts[MAX_FONTS][2]; extern const __flash struct CharZnak struct_fnt[MAX_FONTS-2][223]; .... symbolx=struct_fnt[tekfonts-2][Znak].width + tekkern; Вот пример компиляции Код 1038 symbolx=struct_fnt[tekfonts-2][Znak].width + tekkern; \ 0000002C .... LDI R30, LOW((struct_fnt - 1336)) \ 0000002E .... LDI R31, HIGH((struct_fnt - 1336)) \ 00000030 5221 SUBI R18, 33 \ 00000032 E003 LDI R16, 3 \ 00000034 9F02 MUL R16, R18 \ 00000036 0DE0 ADD R30, R0 \ 00000038 1DF1 ADC R31, R1 \ 0000003A E90D LDI R16, 157 \ 0000003C E012 LDI R17, 2 \ 0000003E 9F14 MUL R17, R20 \ 00000040 2D10 MOV R17, R0 \ 00000042 9F04 MUL R16, R20 \ 00000044 0D11 ADD R17, R1 \ 00000046 0DE0 ADD R30, R0 \ 00000048 1FF1 ADC R31, R17 \ 0000004A 9104 LPM R16, Z \ 0000004C 0F06 ADD R16, R22 \ ??ZnakWidth_1:
|
|
|
|
|
Feb 21 2008, 07:34
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(AHTOXA @ Feb 21 2008, 10:20)  WINAVR как раз в этом очень сильно тормозит. Работа с флешом в нём сделана мегакриво. ... И похоже, что по политическим соображениям никогда не исправится. Насчет кривизны: Из предыдущего утомительного примера: Код static Cm_Xref_t Cm_Xref[Max_cmd+1]= { {cm_Brief,0,0}, // version crc {cm_NetLocate,&Net_ID,EE_Fit+1}, {cm_Set_Selector,&Selector,EE_Fit+1}, /*****************************************/ }; /******************************************/ uint8_t M; M = Cm_Xref[2].ojsize; Так constant propagation оно просчитывает. А стоит появиться адресному выражению - кирдык. Цитата Попробуйте писАть на С, а не на asm сишными словами... Блин, вот зацепил...до сих пор помню.
|
|
|
|
|
Feb 21 2008, 08:43
|

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

|
Цитата(_Pasha @ Feb 21 2008, 03:19)  Вот это совсем другое дело!  Че-то WINAVR тормозит немного. Ну, у avr-gcc код как правило похуже, с этим не спорю. Код #include <stdint.h> #include <avr/pgmspace.h> #define MAX_FONTS 10 // от балды, в приведеннмо примере не было задано. Но оно неважно
// структура данных по символам struct CharZnak { uint16_t sm; // Смещение от начала фонта uint8_t width; // Ширина символа };
extern const uint8_t Fonts[MAX_FONTS][2] PROGMEM; extern const struct CharZnak struct_fnt[MAX_FONTS-2][223] PROGMEM; extern uint8_t tekfonts, tekkern;
uint8_t sx(uint8_t Znak) { return pgm_read_byte( & struct_fnt[tekfonts-2][Znak].width ) + tekkern; } CODE .file "tst.c" __SREG__ = 0x3f __SP_H__ = 0x3e __SP_L__ = 0x3d __tmp_reg__ = 0 __zero_reg__ = 1 .global __do_copy_data .global __do_clear_bss .text .global sx .type sx, @function sx: /* prologue: frame size=0 */ /* prologue end (size=0) */ ldi r25,lo8(3) mul r24,r25 movw r30,r0 clr r1 lds r24,tekfonts ldi r25,lo8(0) ldi r18,lo8(669) ldi r19,hi8(669) movw r20,r24 mul r20,r18 movw r24,r0 mul r20,r19 add r25,r0 mul r21,r18 add r25,r0 clr r1 add r30,r24 adc r31,r25 subi r30,lo8(-(struct_fnt-1336)) sbci r31,hi8(-(struct_fnt-1336)) /* #APP */ lpm r30, Z /* #NOAPP */ lds r24,tekkern add r30,r24 mov r24,r30 ldi r25,lo8(0) /* epilogue: frame size=0 */ ret /* epilogue end (size=1) */ /* function sx size 31 (30) */ .size sx, .-sx /* File "tst.c": code 31 = 0x001f ( 30), prologues 0, epilogues 1 */ Катастрофической я бы разницу не назвал - ни в написании текста, ни в сгенерированном коде. Сравнивать надо одинаковые фрагменты, в приведенном выше куске часть регистров уже "откуда ни возьмись" заполнены и используются, тут всё "автономно". Цитата(_Pasha @ Feb 20 2008, 20:49)  По поводу "писАть на С" - я "С" терпеть не могу еще с 1992 года  Я года с 1987..88 использую и ничего. Что-то писалось чисто асмовое, что-то с делением "это пусть на С, а эта часть на асме". Сейчас если очень хочется - пишу вставку на асме. В последнее время хочется всё реже. Цитата 2. Переопределять стандартные функции НЕ ХОЧУ. Ну извините... Так можно дойти до "а на асме я могу что-то другое делать, пока EEPROM пишется, а С-шная программа станет колом на всё время записи блока, во какой плохой язык С". И начать требоавть стандартную функцию для решения всей своей задачи.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Feb 21 2008, 11:52
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(_Pasha @ Feb 21 2008, 09:34)  Блин, вот зацепил...до сих пор помню. С одной стороны это гут, т.к. хотел обратить Ваше внимание на то, что стиль кода Вашего примера не Сишный, и рад что вышло. Все тоже самое в Сишном стиле можно сделать красивее и быстрее чем на асм, и уже глядя на код нельзя будет его назвать "байдой". Цитата Проблемы-то начинаются с обращения к памяти программ за данными. Этот процесс нигде не автоматизирован, отсюда сложности. да и на асм он не автоматизирован, ровно в той же степени.
|
|
|
|
|
Feb 21 2008, 16:57
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Вернемся к нашей баранине. "Не умеешь-научим, не хочешь-заставим." Заставили. Дописал функцию записи в ЕЕПРОМ, в аккурат по шаблону eeprom_write_block() . Код void EE_Proof_Write_Block(const void *pointer_ram, void *pointer_eeprom, size_t n) { size_t j; uint8_t *src,*dst;
src = (uint8_t*)pointer_ram; dst = (uint8_t*)pointer_eeprom;
for (j=0;j<n;j++) { if (eeprom_read_byte(dst) != *src) eeprom_write_byte(dst,(uint8_t)*src); dst++; src++; } return; } ИТОГО такое: +56 байт. Имея ввиду, что была исходная eeprom_write_block(), вычтем, скажем, байт 16. В добавок, это все можно обрезать, задаваясь размером блока не size_t a uint8_t, если уж очень хотца. Это еще минус байта 4-6. Короче, +32..36 байт. Оптимайзер рулит!!! Может, мы доживем до тех времен, когда свопинг лишний чистится будет... Кстати, кто-нибудь знает, почему многопроходовая оптимизация в GCC не в фаворе? Цитата(defunct @ Feb 21 2008, 14:52)  Все тоже самое в Сишном стиле можно сделать красивее и быстрее чем на асм, и уже глядя на код нельзя будет его назвать "байдой". Боюсь, что не получится. Команд будет поболее - до 30 в полновесном варианте. И надо, чтобы в идеале доступ был не по индексу, а по LookUp, т.к. коды команд могут меняться. Так что от таблицы никуда не денешься. Впрочем, если Вы пальцем ткнете, я пойму, что имеется ввиду...
|
|
|
|
|
Feb 21 2008, 17:49
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(_Pasha @ Feb 21 2008, 19:57)  Код void EE_Proof_Write_Block(const void *pointer_ram, void *pointer_eeprom, size_t n) { size_t j; uint8_t *src,*dst;
src = (uint8_t*)pointer_ram; dst = (uint8_t*)pointer_eeprom;
for (j=0;j<n;j++) { if (eeprom_read_byte(dst) != *src) eeprom_write_byte(dst,(uint8_t)*src); dst++; src++; } return; } Несколько моментов про Ваш код: - вместо цикла for лучше использовать do..while c проверкой size_t n == 0 только в самом начале.. - написав отдельно src++ и вызывая стандартные функции Вы сами себя лишили возможности пользоваться автоинкрементом при доступе к данным в RAM - каждая из функций eeprom_xxx будет ждать флага освобождения EEPROM - каждая из функций eeprom_xxx будет прописывать один и тот же адрес в EEAR попробуйте это отрихтовать и удивитесь лаконичности полученого кода...
|
|
|
|
|
Feb 21 2008, 19:09
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(defunct @ Feb 21 2008, 14:52)  стиль кода Вашего примера не Сишный, и рад что вышло. Нашел. Это паскалевский стиль. Обнаружился древний, почти безусловный рефлекс: как только в программе забрезжит хоть какой-то намек на ввод-вывод, бежим как от огня от перечислительных типов. Применительно к "С" это распространилось и на битовые поля. Так что, могу на любом языке, как на паскале.  Кстати, был еще один рефлекс: клавишу F2 нажимать каждые 15 секунд. Ох и долго избавлялся! Цитата(singlskv @ Feb 21 2008, 20:49)  Несколько моментов про Ваш код:
попробуйте это отрихтовать и удивитесь лаконичности полученого кода... Спасибо! Рихтуем, удивляемся...
|
|
|
|
|
Feb 21 2008, 22:37
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(ReAl @ Feb 21 2008, 11:43)  Ну извините... Так можно дойти до "а на асме я могу что-то другое делать, пока EEPROM пишется, а С-шная программа станет колом на всё время записи блока, во какой плохой язык С". Вотъ, это тоже хотелось бы обсудить, хотя конкретно это, я уже пытался обсуждать, но большинство опонентов высказалось в том смысле, что в IAR удобно обращаться к таким(EEPROM) переменным и все тут... А сейчас я вот задался интересным вопросом, если кто еще не заметил, в новых AVR (mega48/88/......) есть такая фича, можно отдельно стирать ячейку и можно отдельно ее программировать... Вопрос: кто-нить уже пробовал записать в ячейку 2 раза без стирания ? ИМХО, это открывает новые возможности для увеличения количества записи в eeprom, но конечно, этот факт, требует дополнительной проверки, добровольцы есть ?
|
|
|
|
|
Feb 22 2008, 10:47
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Теперь про асм. В контексте многопоточного программирования очень полезно использование итераторов. Пример: Код Iterate_16: ; input xL:xH = counter ; zL:zH = pointer to procedure/iteration body push zL push zh
push xL push xH icall pop xH pop xL
sbiw xL,1 brne iterate_16_1
pop zh pop zL ret
iterate_16_1: push xL push xh leave(Iterate_16_2)
Iterate_16_2: pop xh pop xL pop zh pop zL rjmp Iterate_16 Данный итератор позволяет создавать циклы произвольной вложенности (в зависимости от размеров стека Вашего процесса). При этом подпрограмма -тело цикла вызывается косвенно, и может быть как атомарной, так и передавать управление системе, т.к. все параметры цикла как конструкции находятся в локальном стеке процесса. Пример применения: Код func1: ; blah,blah,blah ret ;................................... func2: ldi16 xL,xh,0x100 ldi16 zL,zh,func1; jmp Iterate_16 ;................................... func3: ldi16 xL,xh,0x0010 ldi16 zL,zh,func2 jmp Iterate_16 В приведенном примере внешний цикл описан в func3, в котором вызывается внутренний цикл, описанный func2. Примерный сишный эквивалент Код int i,j; for(i=0x0010;i >= 0;i--) for(j=0x100;j>=0;j--) func1(); ... но в условиях кооперативного распределения ресурсов P.S. Исправил примеры func2, func3. Была ошибка в загрузке Z. Кому интересно, обратите внимание.
|
|
|
|
|
Feb 22 2008, 15:27
|

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

|
Цитата(Artak @ Feb 22 2008, 01:25)  На C приходится писать int result = (int)a * (int)b; или int result = (int)a * b; (a и b 8 битные) Так как если написать int result = a * b то компилятор согласно правилам языка C сначала генерирует mul а потом обнуляет старший байт (результат умножения двух char получается в виде char и потом толко присвоивается int у) По стандарту языка С те два a,b обязаны быть приведены к int до выполнения команды умножения. Т.е. явне приведения не нужны, результат должен быть правильным. Иначе - в топку компилятор, С хорош стандартом, если для каждого компилятора надо менять привычки - то тогда проще на асме писать. Цитата(Artak @ Feb 22 2008, 01:25)  А код скомплированный в результате int result = (int)a * (int)b; получаетса очень громоздкий, так как умножаяютса два 16 битных числа не обращая внимания на то что в старших байтах нули чтобы решить проблему скорости мне пришлось в проекте написанном на C писать int result = a * b потом из сгенерированного асм листига вручную удалить clr r1 после mul-а и сгенерировать hex фаил в AVR Studio Код #include <stdint.h>
uint16_t mul88(uint8_t a, uint8_t b) { return a*b; } avr-gcc -O2 -S -mmcu=atmega8 Код .text .global mul88 .type mul88, @function mul88: /* prologue: frame size=0 */ /* prologue end (size=0) */ mul r22,r24 movw r24,r0 clr r1 /* epilogue: frame size=0 */ ret Ну да, clr r1... Но уже после переноса результата умножения в пару R25:R24, так как по соглашениям avr-gcc регистр R1 содержит константу 0 (я бы предложил попробовать переделать это хозяйство на два временных регистра __tem_reg_l__, __temp_reg_h__ и R2 как константу 0, чтобы не мучиться каждый раз после умножения, но неизвестно, как занятие R2 скажется на остальном).
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Feb 22 2008, 23:10
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
В общем, наигрался я с "С". Память заканчивается, а проект лежит. Теперь переписываю на асме. Размер кода обещает быть в 2 раза короче (к слову о больших проектах). Да и пишется все по-быстрому, если знаешь, что делаешь. После чего все эти компиляторы, расплодившиеся в последнее время у меня в компе, летят в корзину. То же самое, кстати, касается и pic18 и dspic30/33 и HCS08, коими я иногда страдаю. Компилятор-то, оказывается, в голове...
|
|
|
|
|
Feb 22 2008, 23:55
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(_Pasha @ Feb 23 2008, 02:10)  В общем, наигрался я с "С". Память заканчивается, а проект лежит. Теперь переписываю на асме. Размер кода обещает быть в 2 раза короче (к слову о больших проектах). Такая разница может получиться ТОЛЬКО на вычислительных алгоритмах и в основном в местах где при использовании С необходимо делать преобразование типов, ну или для вариантов когда просто в С нет соответствующего размера типа, например в С нет 3-байтовых переменных... Для остальных вариантов..., ну показывайте код что ли, думаю что после некоторой оптимизации он Вас опять же сможет удивить... Цитата(defunct @ Feb 23 2008, 02:37)  Совет для того чтобы память не заканчивалась в проектах на "C" 1. Делать стек большим, а глобальных переменных поменьше. 2. Использовать heap. Вот здесь хотелось бы разъяснений и поподробнее...(без подколок...), я не понимаю как уменьшить количество глобальных переменных. Я не понимаю как можно использовать heap для уменьшения расхода памяти вместо использования глобальных переменных...
|
|
|
|
|
Feb 23 2008, 00:10
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(singlskv @ Feb 23 2008, 01:55)  Вот здесь хотелось бы разъяснений и поподробнее...(без подколок...), я не понимаю как уменьшить количество глобальных переменных. А что Вы храните в глобальных переменных? Ведь кроме конфигурации там больше нечего хранить. Под все остальное память можно выделить либо в стеке, либо в куче. Цитата Я не понимаю как можно использовать heap для уменьшения расхода памяти вместо использования глобальных переменных... это смотря под что у вас используются глобальные переменные. Допустим имеем дело с неким протоколом где команды(пакеты) могут поступать не дожидаясь ответа на предыдущую команду(пакет). Предположим что глобально выделенно два буфера фиксированного размера (один обрабатывается, во второй принимается сл. команда). Попробуем сократить объем памяти. Выделяем буфер из heap, ведем в него прием. По окончании приема, передаем указатель на этот буфер в функцию обработки, выделяем еще один буфер под прием сл. пакета и ведем прием дальше. В функции обработки - перевыделяем память в соответсвии с реальным размером пакета (не всегда же пакет будет максимально возможной длины), или копируем во временный буфер в стеке, память освобождаем. Итого на лицо экономия памяти на 1 буфер в 99% времени работы нашего устройства (т.к. полных два буфера будет использоваться в очень короткий промежуток времени - только в момент между окончанием приема текущего пакета и запуском функции обработки). Еще пример: допустим по i2c раз в секунду надо что-то записать/прочитать. Выделили буфер из heap - сказали i2c драйверу - читать/писать данные в него. Все остальное время - эта память будет использоваться под что-то другое. и так многие места где используются значительные объемы памяти можно разделять во времени с помощью heap'a - пользовать память эффективно (одну и ту же область для разных целей в разное время).
|
|
|
|
|
Feb 23 2008, 00:43
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(defunct @ Feb 23 2008, 03:10)  А что Вы храните в глобальных переменных? Ведь кроме конфигурации там больше нечего хранить. Как это нечего ? Вот вполне реальный пример, нужно по 4 каналам АЦП делать скользящее среднее по 8 измерениям, ИТОГО: 4 * 8 * 2(байта на измерение) = 64 байта + 4(канала) * 2(байта) = 8 байт для хранения суммы + 1 байт на хранение номера канала + пару байт на всякие сервисные штучки ИТОГО: ~75 байт глобальных переменных.. Пример N2: Есть ~250байт которые есть суть текущее состояние системы, эти параметры могут быть запрошены в любой момент например по протоколу модбас, допустим что модбас работает на скорости 345600, можем ли мы хранить где-нить кроме глобальных переменных эти данные ?
|
|
|
|
|
Feb 23 2008, 00:58
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
А мне просто супер нравится С. По-моему, для МК это совершенно оптимальный уровень абстракции. Здесь чётко видишь что такое объект или структура, что будет делать компилятор. Если необходимо вставить ASM кусок - пожалуйста, сразу видно как к сишным переменным обращаться. С другой стороны - совершенно наплевать как он память распределяет, - пусть сам думает. Как раз с этим не хочется возиться.
Я тоже наигрался с Си пытаясь добиться от него оптимального (как сделал бы сам на асме) кода. Ну кое где проколы есть у него, но он зато выигрывает при большом количестве глобальных переменных. Я не смог тут его сделать.
Не знаю как у вас получается "в два раза" - у меня 10-20%.
Единственное где можно капитально "надрать" компилятор, это если большое количество основных переменных в регистры влезает. Но это ведёт к сложности модификации программы. Что мы и имеем.
С другой стороны - какая песня иметь легко модифицируемый чёткий и ясный проект!!! Вот вы применяете кастрированную ОС своего исполнения. А если применить нормальную. Со всеми наворотами? Ну хорошо "не влазит задача". А можно спросить - куда она не влазит? Если, к примеру в m8, то есть m168/328. Разница - копейки. Если речь идёт о какой-нибудь m640, то ещё меньше разница на m1280/2560. Или на соответствующий ARM перейти со старшей модели тоже не фокус из под С. Короче, безусловно, дело вкуса, но очень спорный момент.
Когда просматриваешь кусочек, то да - кода много. А если сделать полный проект, то ваш выигрыш будет копеечный, а в замен сложная модифицируемость проекта. Си-шный сопровождать - на порядок легче. Это, конечно, моё личное мнение.
|
|
|
|
|
Feb 23 2008, 01:10
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(singlskv @ Feb 23 2008, 02:43)  Как это нечего ? Вот вполне реальный пример, нужно по 4 каналам АЦП делать скользящее среднее по 8 измерениям, ИТОГО: 4 * 8 * 2(байта на измерение) = 64 байта Я конечно не знаю вашей задачи полностью и возможно у вас есть некие строгие требования к сохранению фазы сигнала поэтому нужен КИХ, а я не брезгую БИХ'ом - ибо эффективней с т.з. памяти.. У меня скользящее по восьми измерениям занимает 2* U16 данных и кот наплакал кода. итого 4 канала - 16 байт: код для усреднения по 8-ми точкам: Код pChan->vAverage = (pChan->vSum + 4) >> 3; pChan->vSum += val - pChan->vAverage; но я не меняя размер данных могу и по 16-ти точкам усреднять и по 32-м и по 1024. меняться будет только формула.. Конечно, это не тот случай когда heap чем-то помог, но все же думать об экономии там где это возможно - стоит. Цитата Пример N2: Есть ~250байт которые есть суть текущее состояние системы, эти параметры могут быть запрошены в любой момент например по протоколу модбас, допустим что модбас работает на скорости 345600, можем ли мы хранить где-нить кроме глобальных переменных эти данные ? Статистика / параметры системы - это есть конфигурация... Стало быть легально делать их глобальными.
|
|
|
|
|
Feb 23 2008, 11:09
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Из приведенного мной первого поста видно, что проигрыш по памяти - почти двукратный. Правда .... 1) я не оптимизировал ... 2) На оба стэка выделил значительно больше памяти, чтобы не искать потом мучительно .... 3) На асме стек не учитывал ...
Тем не менее, если это всё поучитывать + взять равноценные проекты = всё равно коэффициент 1.3~1.4 думаю найболее реальный. Но памяти сейчас в микроконтроллерах вполне достаточно. Как правило, на большинстве задач на асме она остаётся, а на Си её просто достаточно. В тех же задачах где требуется много памяти, - не спасает использование ASMа, надо вешать внешнюю.
|
|
|
|
|
Feb 24 2008, 18:47
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(defunct @ Feb 23 2008, 04:10)  Я конечно не знаю вашей задачи полностью и возможно у вас есть некие строгие требования к сохранению фазы сигнала поэтому нужен КИХ, а я не брезгую БИХ'ом - ибо эффективней с т.з. памяти.. У меня скользящее по восьми измерениям занимает 2* U16 данных и кот наплакал кода. итого 4 канала - 16 байт: код для усреднения по 8-ми точкам: Я тоже пользуюсь разными вариантами... иногда честное скользящее среднее иногда экспоненциальный фильтр. Цитата Код pChan->vAverage = (pChan->vSum + 4) >> 3; pChan->vSum += val - pChan->vAverage; и так тоже пользуюсь, тока для уменьшения рассчетов, я в таких случаях считаю что, все вычисляется с коэфициентом (8 ну или сколько мы там решили), тогда никагого оверхеда не происходит в принципе... Цитата Конечно, это не тот случай когда heap чем-то помог, но все же думать об экономии там где это возможно - стоит. Ну вот я и хочу увидеть эту экономию, и особенно в сравнении с асм, не, я конечно понимаю что для моих задач никакой экономии просто не будет, Скажем так, хочу увидеть задачи (все таки контроллерные... хоть чуть...) где использование heap будет нужным/оправданным..
|
|
|
|
|
Feb 24 2008, 19:50
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(singlskv @ Feb 24 2008, 22:47)  Скажем так, хочу увидеть задачи (все таки контроллерные... хоть чуть...) где использование heap будет нужным/оправданным.. Видете ли. Совершенно вам не в пику пишу. Это просто вопрос подходов. Вы его не приемлете (...хоть чуть..), а я считаю использование HEAP вполне оправданным. Начнём с того, что использование HEAP на Си практически не увеличивает загрузку процессора. Поэтому вреда от этого никакого. А отсюда следует что его применение возможно везде. Теперь второе. В Си, в отличие от ASM, вы не контролируете память. Не знаю как правильнее выразится. Давайте лучше на примере. Представьте, что вы пишите проект на ASM для которого чем больше свободной памяти - тем лучше. Вы его пишете и распределяете память вручную. Выделяете кусок в виде .byte, смотрите на стек и так далее. Задачу эту вы написали например на м64. Через некоторое время вы переползли на м640 где памяти вдвое больше. Вам придётся минимум пересчитать остаток свободной памяти и поменять одну константу. Можно так поступить и на Си. А можно использовать HEAP и тогда при переносе вам ничего менять не придётся. Не придётся ничего менять и пересчитывать также и при внесении изменений в проект . Но это пример, где можно или так или так. Приведу пример, где я примененяю HEAP. Представьте себе что контролер выполняет команды. Каждая команда имеет разную длину. От десятка байт до нескольких килобайт. Имеется очередь активных команд состоящая из 10 команд. Каждая команда имеет время старта и время смерти (начала и конца исполнения). То есть находящаяся в очереди команда может исполняться а может не исполняться, но после смерти удаляется из очереди и соответственно подгружается новая команда. Источник поступления команд может быть разный (причём одновременно) RS485, SD, Ethernet, Rf, EEPROM. Здесь, на мой взгляд HEAP очень органично вписывается. Я делаю дефрагментацию (удаление дырки от выполненной команды), храню буфер активных команд (переменной длины) и буфер загружаемых команд (дабы к моменту когда понадобится новая команда - она уже находилась в контроллере - синхронизация загрузки и исполнения) тоже переменной длины. Чем более мощные команды я могу исполнять, тем больше возможности устройства. А это зависит от синхронизации загрузки и, соответственно от оптимальности распределения буферов внутри контроллера.
|
|
|
|
|
Feb 24 2008, 21:03
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(singlskv @ Feb 24 2008, 20:47)  Скажем так, хочу увидеть задачи (все таки контроллерные... хоть чуть...) где использование heap будет нужным/оправданным.. Ну так я ж выше привел такие примеры. Везде где есть пакетная обработка - использование heap'a (точнее сказать использование динамической памяти) экономит расход. Цитата Здесь, на мой взгляд HEAP очень органично вписывается. Я делаю дефрагментацию (удаление дырки от выполненной команды), храню буфер активных команд (переменной длины) Один из способов борьбы с фрагментацией - секторный подход - форматируем некую область памяти на мотив секторов на диске фиксированного размера, потом выделяем и освобождаем их. плюсы - быстро, нет проблем с фрагментацией, простой менеджер памяти. минусы - overhead на маленьких объемах, и ограничение по объему.
|
|
|
|
|
Feb 24 2008, 22:23
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(SasaVitebsk @ Feb 24 2008, 22:50)  Начнём с того, что использование HEAP на Си практически не увеличивает загрузку процессора. Поэтому вреда от этого никакого. А отсюда следует что его применение возможно везде. Увеличивает и очень прилично... Я как бы понимаю необходимость использования heap например при загрузке картинки или приеме пакета неизвестной длинны через Ethernet... но для реалтайма непонимаю... Цитата Теперь второе. В Си, в отличие от ASM, вы не контролируете память. Не знаю как правильнее выразится. Как это не контролирую ? Лично я контролирую, и это не сильно отличается от асм, ну конечно printf, итд я просто не пользуюсь. Цитата Представьте, что вы пишите проект на ASM для которого чем больше свободной памяти - тем лучше. Вы его пишете и распределяете память вручную. Выделяете кусок в виде .byte, смотрите на стек и так далее. Задачу эту вы написали например на м64. Через некоторое время вы переползли на м640 где памяти вдвое больше. Вам придётся минимум пересчитать остаток свободной памяти и поменять одну константу. Можно так поступить и на Си. А можно использовать HEAP и тогда при переносе вам ничего менять не придётся. Не придётся ничего менять и пересчитывать также и при внесении изменений в проект . Но это пример, где можно или так или так. Эээ..., ну просто стараюсь писать так чтобы перенос c одного чипа на другой заключался только в переназначении пинов контроллера, обычно получаеться, правда иногда при переходе с более "старшего" чипа на младший приходится таймеры перенастраивать... Цитата Приведу пример, где я примененяю HEAP. Представьте себе что контролер выполняет команды. Каждая команда имеет разную длину. От десятка байт до нескольких килобайт. Имеется очередь активных команд состоящая из 10 команд. Каждая команда имеет время старта и время смерти (начала и конца исполнения). То есть находящаяся в очереди команда может исполняться а может не исполняться, но после смерти удаляется из очереди и соответственно подгружается новая команда. Источник поступления команд может быть разный (причём одновременно) RS485, SD, Ethernet, Rf, EEPROM Вот и я о том же, примерно, вопрос в том где это (использование heap) применимо/эфективно, Вот для EEPROM, ИМХО, применимо/эфективно, только это не совместимо с использованием стандартного для IAR _flash...
|
|
|
|
|
Feb 25 2008, 13:23
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(singlskv @ Feb 25 2008, 02:23)  Увеличивает и очень прилично... Тогда давайте сначала с терминами определимся. HEAP - просто слово и всё. Термин. С момента как вам понадобилась вся память, независимо от того сколько её, - вы уже работаете с HEAP, нравится вам этот термин или нет. А вот как вы с ней работаете - это уже вопрос. Если вы работаете через стандартные процедуры malloc/free (как я понял об этом идёт речь), то никаких накладных расходов практически нет так как malloc фактически проверяет есть ли у вас нужный объём и прибавляет к указателю на свободное место этот объём. А free просто записывает новое значение указателя (с контроллем). Поскольку процедуры эти - не ногодрыганье, то, очевидно, что это времени не отнимает. Если вы не используете стандартный диспетчер памяти, то вам приходится городить свой. Явно или неявно. Если не явно, то, в простейшем случае, вам придётся один раз при инициализации выделить всю память и в процессе работы контролировать границу, куда залазить нельзя (кольцевой буфер). В более сложной реализации вам придётся определять размер объекта и т.д., что по сути и делает стандартный набор средств. В ещё более сложной реализации (дефрагментация) вам придётся реализовать свой диспетчер. Теперь опять возвратимся к терминам. Итак мы работаем с HEAP но не хотим в этом признаваться. Тогда у нас несколько путей. 1) вписываем работу с памятью прямо в тексте проги, 2) используем процедуры с уникальным названием, 3) используем стандартные названия (даже если процедуры пишем сами). По-моему, лучше 3 путь. Всё таки это опыт программеров и общие термины. А вообще, и это мне тоже очень нравится, Си очень демократичный язык. Возьмите в своём проекте напишите процедуру malloc и вот вам стандартная и в тоже время более качественная (с вашей точки зрения) реализация. PS: Не вижу ни каких проблем и с EEPROM и c FLASH. Можно сделать всё что угодно. Вплоть до реализации потоков и файлов. Или параллельной кучи и даже общей кучи. В винде вон часть памяти на винте находится территориально. А у меня, например EEPROM внешняя и внутренняя объёденены в общее пространство. Всё в ваших руках. Не нравится - перепиши.
|
|
|
|
|
Feb 26 2008, 09:29
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
По-моему, с heap основная проблема в том, как рассчитать максимальную загрузку. И что делать, если память кончилась - допустимо ли отклонить запрос. Например, пример defunct - в 99% времени обработчику нужен только 1 пакет. Если таких обработчиков 5 - то какая куча нам нужна? На 10 пакетов? В этом случае мы не выигрываем по сравнению со статическим выделением памяти. А если, к примеру, на 6 пакетов - то что делать в ситуации, когда пакетов 7. Или можно доказать, что ни в один момент времени не будет больше 6 пакетов... Следующий нюанс - если вы храните объекты разного размера, то можно получить ситуацию, когда из-за фрагментации невозможно будет разместить новый объект при наличии значительного кол-ва свободной памяти. Тут по-ходу придется мастерить что-то свое, стандартные механизмы работы с кучей не подойдут. Лично я использую "самописную" кучу для пользовательского интерфейса - например, есть основной экран, из него можно попасть в меню, из него - в другое меню, оттуда - в настройку параметра, из нее иногда - в настройку еще одного параметра. При этом объекты уничтожаются в порядке, обратном созданию, соответственно реализация очень проста и нет фрагментации.
|
|
|
|
|
Feb 26 2008, 10:39
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(Непомнящий Евгений @ Feb 26 2008, 12:29)  .............................. Примерно это я и имел в виду. Просто в моих задачах выгоднее статически выделять память. А про накладные расходы по работе с кучей я имел в виду не момент выделения памяти, там действительно все быстро в случае если ее хватает и есть непрерывный кусок нужного размера, а вопрос по сборке мусора после произвольных выделений/освобождений памяти, и вот там совсем все не быстро/просто.
|
|
|
|
|
Feb 26 2008, 11:21
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(Непомнящий Евгений @ Feb 26 2008, 11:29)  Например, пример defunct - в 99% времени обработчику нужен только 1 пакет. Если таких обработчиков 5 - то какая куча нам нужна? На 10 пакетов? В этом случае мы не выигрываем по сравнению со статическим выделением памяти. А если, к примеру, на 6 пакетов - то что делать в ситуации, когда пакетов 7. Или можно доказать, что ни в один момент времени не будет больше 6 пакетов... Можно доказать, что не 10, а 9 пакетов будет достататочно всегда, а это уже экономия: Вариант когда на все 5 интерфейсов одновременно приходит пакет, начинается обработка самого приоритетного интерфейса, пакет перекладывает в стек, остальные 4 интерфейса "отцепляют" свой текущий приемный буфер (ставят пакет в очередь на обработку) и выделяют себе другой. Итого 5+4 = 9. Минимальное же количество необходимых буферов зависит от скорости интерфейсов и от скорости обработки пакетов - минимум который можно достичь - 6.. На практике - 7 буферов будет достаточно.
|
|
|
|
|
Feb 26 2008, 11:50
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(singlskv @ Feb 26 2008, 14:39)  Просто в моих задачах выгоднее статически выделять память. Если динамическое распределение не нужно, то и не надо его за уши притягивать. Я, к примеру, применять динамическое распределение при построении меню не стал бы, а вот Непомнящий Евгений реализовал и, как видите, доволен результатом. Это говорит, что мы все разные. А нужный инструмент выбирает программист исходя из видения своего проекта. А качество реализации зависит от того, как программист этим инструментом пользуется. А знания, я в этом совершенно убеждён, никогда не являются лишними. Если ты знаешь, то точно можешь ответить, надо им пользоваться или нет. А если не знаешь, то не можешь и оценить.
|
|
|
|
|
Feb 26 2008, 20:26
|

Участник

Группа: Свой
Сообщений: 41
Регистрация: 17-01-08
Пользователь №: 34 178

|
Цитата(Rst7 @ Feb 22 2008, 10:29)  В IAR'е можете написать __multiply_unsigned( a , b ) - это непосредственно сгенерит mul. Для портабельности не забудьте на этапе компиляции проверить текущий процессор и компилятор, и если это не AVR и не IAR, сделать #define __multiply_unsigned(var1,var1) ((var1)*(var2)). Про остальные умножения можете прочитать в доке по IAR'у, ключевое слово "Intrinsic functions" Большое спасибо!!! пользовалься CodeVisionAVR перейду на IAR
Сообщение отредактировал Artak - Feb 26 2008, 20:40
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|