| Микроконтролери и електроника http://mcu-bg.com/mcu_site/ |
|
| GNU C/C++ static inline функция http://mcu-bg.com/mcu_site/viewtopic.php?f=3&t=8808 |
Страница 1 от 2 |
| Автор: | ¶ [ Вто Апр 19, 2011 3:12 am ] | ||||||||||||||||||
| Заглавие: | GNU C/C++ static inline функция | ||||||||||||||||||
Имам следната функция, която е static inline:
Така написана обаче генерира твърде много инструкции, на всичкото отгоре въпреки inline директивата се прави извикване към подпрограма, което води до още повече генерирани инструкции. Процесора пък ( PIC32 ) си има ефективна инструкция MADD. Горната функция написана като макрос с вградения асемблер изглежда така: 1) #define MADD64( s, x, y) __asm__ volatile ("madd %2,%3" : "+l" ( ((Int64*)&s)->lo ), "+h" ( ((Int64*)&s)->hi ) : "%r" (x), "r" (y)) където Int64 е помощно обединение дефинирано така:
Макроса генерира два пъти по-малко инструкции отколкото ако е static inline функция. Дотук добре, но се сетих, че вместо с помощния тип данни Int64 макроса MADD64 може да се напише и така: 2) #define MADD64( s, x, y) __asm__ volatile ("madd %2,%3" : "+l" ( ((uint32*)&s)[0] ), "+h" ( ((int32*)&s)[1] ) : "%r" (x), "r" (y)) което би трябвало да е същото за C/C++ . Да, ама не е. Ако са изключени оптимизациите вариант 2) генерира правилно инструкциите, ако обаче се включат оптимизациите на вариант 2) резултата от от madd инструкцията не се присвоява на s[0] & s[1], ами си остава в регистрите на процесора,т.е. смята каквото смята и накрая резултата си остава в регистрите, а s[0] & s[1] си стоят непокътнати. Ключовата дума volatile уж би трябвало да откаже компилатора от оптимизация, но не го прави. Вариант 1) ме устройва напълно, но в този си вид се налага да се правят множество промени по сорс файловете, затова ми се ще да запазя първоначалния им вид и вместо вариант 1) да се използва вариант 2), защото при 2) се прави промяна само в един заглавен файл. Така ще мога да запазя съвместимост тъй като кода ще се търкаля евентуално на STM32 и TMS320 , освен на PIC32 ( естествено макроса ще използва съответните MADD на STM32 & TMS320 ). Та въпроса ми е как да "излекувам" GCC-то, че да не ми "оптимизирва" резултата от madd и да го оставя в регистрите ? |
|||||||||||||||||||
| Автор: | miro_atc [ Вто Апр 19, 2011 9:56 am ] | |||||||||
| Заглавие: | ||||||||||
Честно казано не разбирам много от нещата дето си написал, но аз бих го направил с inline static функция чието съдържание е на асемблер. Слагаш я в хедър също като макроса, но е по-удобна за дебъгване (при изключени оптимизации естествено). При свързване пък функцията ти дава малко повече свобода с параметрите. Един бърз преглед в нета дава това:
Сега това е за АРМ... и в интерес на истината аз го правя по друг начин. Вместо "+" за read-write пък после "&" за write only ... бих използвал "=r(output_var) " в изходните ригистри и "0"(input_var) при входните. Така един регистър при влизане се инициализира с една променлива, а при излизане стойността му може да се запише в друга (или същата). Удобното на това да е функция е, че може да си позволиш междинни променливи/присвоявания като това "u.w64 = sum64" - тия неща после се оптимизират винаги когато има възможност. Сега в твоя случай тия "l"и "h" не ги разбирам (дали трябва за съответната инструкция да се указва типа на регистъра)... При входните регистри имаш нещо дето също не разбирам "%r" (x) - какъв е тоя процент? Тъй де направи си колкото искаш локални променливи. Вържи ги с асемблера както трябва, а другото го остави на компилатора... |
||||||||||
| Автор: | Реконструктор [ Вто Апр 19, 2011 10:15 am ] | |||||||||
| Заглавие: | Re: GNU C/C++ static inline функция | |||||||||
Ами скромният ми опит е с AVR, естествено. |
||||||||||
| Автор: | woody [ Вто Апр 19, 2011 1:56 pm ] |
| Заглавие: | |
Пирев, на компот без MIPS GCC съм в момента, но ето ти случаен съвет - виж http://www.mips.com/media/files/MD00565-2B-MIPS32-QRC-01.01.pdf. На втората страница има малък template "INVOKING MULT AND MADD INSTRUCTIONS FROM C" - пробвай така, като естествено за резултат си връщай всичките 64 бита. Ако имаш късмет компилаторът да разпознае template-а, ще минеш и без асемблери. Ако не мине номерът, ще трябва да си поиграеш с различни варианти на вградения асемблер. Усложнението е от това, че натрупването става във вътрешен акумулатор, който предварително трябва да бъде зареден с начална стойност от нормални регистри, и след N натрупвания да бъде трансфериран през нормални регистри към памет. Нямаш идея на всяка итерация да записваш по паметта. Можеш да пробваш __asm__ volatile блок, в който почваш със зареждане на акумулатора от два регистъра, нужния брой madd, и после трансфер на акумулатора към два регистъра, като оставяш регистрите GCC да ги избира. |
|
| Автор: | ¶ [ Вто Апр 19, 2011 4:03 pm ] | |||||||||||||||||||||||||||
| Заглавие: | ||||||||||||||||||||||||||||
Проблема е в генерирания код. Ето как изглежда като static inline:
Но така, при включени оптмизации, static online ми генерира 47 инструкции, докато макроса само 17, a MADD64 се вика около 200 хил. пъти в секунда. Разликата е повече от 50% в бързодействието. Не че не ми е по-удобно да използвам static inline, но много код отива там. По принцип на мен въобще не ми е ясно как се работи с вградения асемблер, добавят се едни параметри дето от разни примери се опитвам да разгадая какво вършат. Например "+l" и "+h" указват къде да се съхрани резултата, MIPS32 ядрото има 2 регистъра, hi & lo, които се използват за натрупване на резултата от MADD. А "%r" въобще не знам какво значи Реконструктор, не мога да си позволя да заделя глобално регистри само заради тази операция. MADD инструкцията се вика лесно, примерно ето една последователност на чист асемблер ( не е вградения имам в предвид под "чист" ):
Ще пробвам по-късно посочения от woody пример да да видя с него какво се компилира. В краен случай ще си напиша една функция на "чист" асемблер ... |
||||||||||||||||||||||||||||
| Автор: | woody [ Вто Апр 19, 2011 4:11 pm ] |
| Заглавие: | |
Синтаксисът на вградения асемблер на GCC е специфичен за всяка архитектура. Малко А-Б-В: http://www.ibiblio.org/gferg/ldp/GCC-In ... HOWTO.html Оттам се заравяш по нета да намериш буквичките конкретно за MIPS32. Използвай само в краен случай. EDIT: l/h са двете части на акумулатора. Плюсът обаче така без ровене няма да се сетя. Аз също гледам да бягам много от такива специфични заключвания, освен ако няма голяма файда. |
|
| Автор: | ¶ [ Вто Апр 19, 2011 4:24 pm ] |
| Заглавие: | |
Template на woody също генерира много код, 44 инструкции. Явно ще трябва да си го направя това парченце код на асемблер в отделен файл ( ако не преборя вградения асемблер де ). |
|
| Автор: | woody [ Вто Апр 19, 2011 5:02 pm ] | |||||||||
| Заглавие: | ||||||||||
Пробва ли с нещо от рода на:
Т.е. да не караш всеки път да пренася акумулатора през регистри, ами да си трупа в него. |
||||||||||
| Автор: | tgi [ Вто Апр 19, 2011 6:54 pm ] | |||||||||
| Заглавие: | ||||||||||
Поне за един компилатор знам, че вграденият му асемблер не е баш асемблер, а правел "еквивалент на". Това го беше изобретил един тъпанар дето до някое време му отговарях на поствете в CAE, ама то беше отдавна де. Имам спомен, че и други са го последвали - тоя специално работеше за ARM. |
||||||||||
| Автор: | gicho [ Вто Апр 19, 2011 9:19 pm ] |
| Заглавие: | |
Въобще случва ли се inline-ването или не? Имаше специален warning на GCC който предупреждава ако по някаква причина функция, дето е обявена за инлайн, не се инлайн-не - дали поради настройки или други причини. Включи го и виж дали няма да изквичи някъде че нещата не са това което очакваш. Отделно GCC искаше самата функция да е дефинирана и декларирана преди реда, в който я викаш. Иначе казва нещо от сорта "тялото на функцията не е налично". Т.е. тия функции е хубаво да са в хедър, и да са подредени според "dependencies" - първо тази която не зависи от никоя друга и т.н. Ако вече е инлайната успешно, можеш да видиш разни атрибути на функциите - имаше "naked" и подобни. Ама поне за АРМ не са ми трябвали и компилатора се справя добре дори на -O1. При АРМ има значение конвенцията за викане, най-стандартните са доста бавни. Но все си мисля че за инлайн не трябва да има значение това. |
|
| Автор: | miro_atc [ Вто Апр 19, 2011 9:22 pm ] | ||||||||||||||||||
| Заглавие: | |||||||||||||||||||
Пирев, много неща не са ми ясни, но GCC-то не е сред тези неща В случая не ми е ясна архитектурата. От това дето виждам на пръв поглед е, че има някакъв акумулатор, който не е част от обикновените регистри. Ако е така, това налага някои ограничения защото вероятно акумулаторът не е част от calling конвенцията. Другото което не ми е ясно дали ти искаш да натрупваш циклично без да презареждаш акумулатора. Ако искаш най-вероятно няма да стане като "викане на функция в цикъл". По подразбиране GCC винаги се съобразява с calling конвенцията. Демек щом имаш функция, тя си приема параметри и връща резултата чрез съответните регистри. GCC прави изключение от това правило за static и inline функции - тогава може да използва други по-удобни регистри вместо стандартните. Но не знам дали би вкарал и акумулатора в тая игра. Честно казано ме съмнява... Що се отнася до асемблера там нещата са прости - имаш:
Елементите от 3-та списъка получават номера (от нула) и ако искаш да ползваш даден елемент в асемблера го ползваш с %номерче. Има го в нета да не обяснявам всички. Според мен така трябва да изглежда твоя код
Където in.lo & in.hi са ти началните стойности, а out.* са резултатите. Може да подадеш едни и същи променливи за in и out ако искаш. В случая има една особеност на GCC че не може един и същ регистър да го има в два списъка, а пък аз държа ACC да го има и като входен и като изходен. Та затова първо слагам примерно "=l" като изходен, това е с номерче нула, т.е. ако се ползваше в асемблера щеше да е %0. А пък в следващия списък на входните неща мога да го ползвам като "0". Накратко искам от GCC да зареди в ACC.lo променливата in.lo и in.hi в ACC.hi. После да извика асемблерската инструкция и накрая да запише резултатите в оут. Забележи, че няма нужда от инструкциите mtlo/mflo/mthi... - то ще си ги генерира както трябва. И ще ги спести ако не трябва. BTW towa "%r" (x)- процента в случая би трябвало да указва, че тоя параметър (x) и следващия (y) са взаимно заменяеми операнди. При ARM това няма абсолютно никакъв смисъл... все регистри и файда от разменянето им няма никаква. Не виждам причина и при MIPS да ги разменя... В крайна сметка разликата с твоя код остава само това че описваш акумулатора само в изходната секция, аз го правя и в двете, за да не пести нито зареждане нито изваждането на резултата... А и още нещо - asm volatile се слага когато имаш някаква "магия" и инструкциите *трябва* да се извикват независимо дали е нужен резултата. Демек при volatile винаги ще ги има асемблерските инструкции, но това не значи, че не може да се оптимизира зареждането на параметрите и спасяването на резултата... В твоя случай нямаш магия, така че няма нужда от volatile. |
|||||||||||||||||||
| Автор: | ¶ [ Вто Апр 19, 2011 10:30 pm ] | |||||||||
| Заглавие: | ||||||||||
Нищо не твърдя по този въпрос Така, по проблемите: 1). static inline, както и да въртя, с inline, __inline, volatile, __attribute__(always_inline) и т.н., компилатора упорито си генерира стандартна функция, което лапа доста инструкции ( в различните варианти между 44 и 47 ). С макрос кода се сведе до 12 инструкции. Така че засега не ми се бори повече с static inline, явно няма да стане. 2) връщането на резултата, с макроса винаги го връща където го очаквам, без значение дали е включена оптимизацията, докато static inline го скрива при включена оптимизация. Като ми остане време ще попрочета от линка на woody за AT&T синтаксиса на асемблера на GCC-то. 3) Резултата не е един и не мога просто да завъртя цикъл и да трупам всичко в хардуерния акумулатор ( hi & lo ). Пазят се 4-ри резултата, така че след всяко MADD резултата от [hi:lo] трябва да се прехвърли в клетки в паметта. Грубо сметнато, за 1 секунда MADD се вика около 1378944 пъти, по 12 инструкции е 16547328 инструкции, по 12.5ns на инструкция прави 206.84ms, или 20% от процесорното време. С static inline яде > 40% от процесорното време. Пак казвам, учуден съм че static inline не генерира inline код, къде е причината все още не знам. Възможно е GCC компилатора на Microchip да е поскопен повече отколкото трябва, имам работеща версия на Macragior GCC за MIPS, като остане време ще проверя и там какво става. |
||||||||||
| Автор: | miro_atc [ Сря Апр 20, 2011 8:13 am ] | |||||||||
| Заглавие: | ||||||||||
Значи inline не се inline-ва докато не включиш оптимизациите. Това си е ОК. Важното е да се оптимизира като ги включиш... btw Без оптимизации GCC генерира много код, ама пък тия 45-47 инструкции сякаш са твърде много. На твое място бих разгледал какво точно прави в пролога и епилога на функцията. При АРМ примерно изключвам фреймирането на стека щото не е необходимо. Много добре си се справя и без него |
||||||||||
| Автор: | Реконструктор [ Сря Апр 20, 2011 11:15 am ] | |||||||||
| Заглавие: | ||||||||||
Инлайна не е задължителен. В M$ компилатора си има една хубава директива, _forceinline, която забранява на компилатора да мисли и решава и директно третира кода като дефинитивна ф-я. |
||||||||||
| Автор: | woody [ Сря Апр 20, 2011 1:18 pm ] | |||||||||||||||||||||||||||
| Заглавие: | ||||||||||||||||||||||||||||
Пробвах на Микрочипския, както е инсталиран покрай техен кит отпреди време - генерира бозици (постоянен трансфер към/от акумулатора - прилича като на липсваща оптимизация), че и задължителния inline игнорира. Нещо такова виждал ли си в съобщенията, в син цвят: warning: Compiler option ignored due to an expired standard-evaluation preiod warning: Disable the option or visit http://www.microchip.com/c32 to purchase a full standard-edition license EDIT: -O3 -fomit-frame-pointer
|
||||||||||||||||||||||||||||
| Страница 1 от 2 | Часовете са според зоната UTC + 2 часа [ DST ] |
| Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |
|