|
|
  |
Сопоставление проектов С и АСМ, Немного цифр |
|
|
|
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, т.к. коды команд могут меняться. Так что от таблицы никуда не денешься. Впрочем, если Вы пальцем ткнете, я пойму, что имеется ввиду...
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|