|
Виж темите без отговор | Виж активните теми
Дата и час: Вто Юли 28, 2026 1:17 am
GNU C/C++ static inline функция
| Автор |
Съобщение |
|
¶
Ранг: Форумен бог
Регистриран на: Пет Фев 25, 2005 1:58 pm Мнения: 4585 Местоположение: US
|
 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 и да го
оставя в регистрите ?
_________________ Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....
|
| Вто Апр 19, 2011 3:12 am |
|
 |
|
miro_atc
Ранг: Форумен бог
Регистриран на: Нед Фев 26, 2006 6:52 pm Мнения: 11266 Местоположение: Добрич
|
Честно казано не разбирам много от нещата дето си написал, но аз бих го направил с inline static функция чието съдържание е на асемблер.
Слагаш я в хедър също като макроса, но е по-удобна за дебъгване (при изключени оптимизации естествено). При свързване пък функцията ти дава малко повече свобода с параметрите.
Един бърз преглед в нета дава това:
Сега това е за АРМ... и в интерес на истината аз го правя по друг начин. Вместо "+" за read-write пък после "&" за write only ... бих използвал "=r(output_var)
" в изходните ригистри и "0"(input_var) при входните. Така един регистър при влизане се инициализира с една променлива, а при излизане стойността му може да се запише в друга (или същата).
Удобното на това да е функция е, че може да си позволиш междинни променливи/присвоявания като това "u.w64 = sum64" - тия неща после се оптимизират винаги когато има възможност.
Сега в твоя случай тия "l"и "h" не ги разбирам (дали трябва за съответната инструкция да се указва типа на регистъра)... При входните регистри имаш нещо дето също не разбирам "%r" (x) - какъв е тоя процент?
Тъй де направи си колкото искаш локални променливи. Вържи ги с асемблера както трябва, а другото го остави на компилатора...
|
| Вто Апр 19, 2011 9:56 am |
|
 |
|
Реконструктор
Ранг: Форумен бог
Регистриран на: Съб Сеп 25, 2004 12:32 pm Мнения: 8382 Местоположение: София
|
 Re: GNU C/C++ static inline функция
Ами скромният ми опит е с AVR, естествено.  Единственият начин, по който успях да накарам gcc да не пипа дадени регистри, беше с дефиниране на глобални променливи като register volatile. Наистина не ги пипа, но регистрите, които си посочил, стават неизползваеми за компилатора.
|
| Вто Апр 19, 2011 10:15 am |
|
 |
|
woody
Ранг: Форумен бог
Регистриран на: Вто Юли 31, 2007 2:55 pm Мнения: 1792 Местоположение: София
|
Пирев, на компот без 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 1:56 pm |
|
 |
|
¶
Ранг: Форумен бог
Регистриран на: Пет Фев 25, 2005 1:58 pm Мнения: 4585 Местоположение: US
|
Проблема е в генерирания код. Ето как изглежда като static inline:
Но така, при включени оптмизации, static online ми генерира 47 инструкции, докато макроса само 17, a MADD64 се вика около 200 хил. пъти в секунда. Разликата е повече от 50% в бързодействието. Не че не ми е по-удобно да използвам static inline, но много код отива там. По принцип на мен въобще не ми е ясно как се работи с вградения асемблер, добавят се едни параметри дето от разни примери се опитвам да разгадая какво вършат. Например "+l" и "+h" указват къде да се съхрани резултата, MIPS32 ядрото има 2 регистъра, hi & lo, които се използват за натрупване на резултата от MADD. А "%r" въобще не знам какво значи  , гледах някакъв пример, че го използват и аз така го направих, че и работи на всичкото отгоре  Къде мога да прочета всичките тези "=r","%r","+&r" и т.н. как се интерпретират ? Реконструктор, не мога да си позволя да заделя глобално регистри само заради тази операция. MADD инструкцията се вика лесно, примерно ето една последователност на чист асемблер ( не е вградения имам в предвид под "чист" ):
Ще пробвам по-късно посочения от woody пример да да видя с него какво се компилира.
В краен случай ще си напиша една функция на "чист" асемблер ...
_________________ Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....
|
| Вто Апр 19, 2011 4:03 pm |
|
 |
|
woody
Ранг: Форумен бог
Регистриран на: Вто Юли 31, 2007 2:55 pm Мнения: 1792 Местоположение: София
|
Синтаксисът на вградения асемблер на GCC е специфичен за всяка архитектура.
Малко А-Б-В:
http://www.ibiblio.org/gferg/ldp/GCC-In ... HOWTO.html
Оттам се заравяш по нета да намериш буквичките конкретно за MIPS32. Използвай само в краен случай.
EDIT: l/h са двете части на акумулатора. Плюсът обаче така без ровене няма да се сетя. Аз също гледам да бягам много от такива специфични заключвания, освен ако няма голяма файда.
|
| Вто Апр 19, 2011 4:11 pm |
|
 |
|
¶
Ранг: Форумен бог
Регистриран на: Пет Фев 25, 2005 1:58 pm Мнения: 4585 Местоположение: US
|
Template на woody също генерира много код, 44 инструкции. Явно ще трябва да си го направя това парченце код на асемблер в отделен файл ( ако не преборя вградения асемблер де ).
_________________ Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....
|
| Вто Апр 19, 2011 4:24 pm |
|
 |
|
woody
Ранг: Форумен бог
Регистриран на: Вто Юли 31, 2007 2:55 pm Мнения: 1792 Местоположение: София
|
Пробва ли с нещо от рода на:
Т.е. да не караш всеки път да пренася акумулатора през регистри, ами да си трупа в него.
|
| Вто Апр 19, 2011 5:02 pm |
|
 |
|
tgi
Ранг: Форумен бог
Регистриран на: Нед Юни 10, 2007 2:22 pm Мнения: 6492 Местоположение: София
|
Поне за един компилатор знам, че вграденият му асемблер не е баш асемблер, а правел "еквивалент на".
Това го беше изобретил един тъпанар дето до някое време му отговарях на поствете в CAE, ама то
беше отдавна де. Имам спомен, че и други са го последвали - тоя специално работеше за ARM.
_________________------------------- www.tgi-sci.com------------------- http://www.flickr.com/photos/didi_tgi/
|
| Вто Апр 19, 2011 6:54 pm |
|
 |
|
gicho
Ранг: Форумен бог
Регистриран на: Пон Мар 13, 2006 1:59 pm Мнения: 3867 Местоположение: Габрово
|
Въобще случва ли се inline-ването или не?
Имаше специален warning на GCC който предупреждава ако по някаква причина функция, дето е обявена за инлайн, не се инлайн-не - дали поради настройки или други причини. Включи го и виж дали няма да изквичи някъде че нещата не са това което очакваш.
Отделно GCC искаше самата функция да е дефинирана и декларирана преди реда, в който я викаш. Иначе казва нещо от сорта "тялото на функцията не е налично". Т.е. тия функции е хубаво да са в хедър, и да са подредени според "dependencies" - първо тази която не зависи от никоя друга и т.н.
Ако вече е инлайната успешно, можеш да видиш разни атрибути на функциите - имаше "naked" и подобни. Ама поне за АРМ не са ми трябвали и компилатора се справя добре дори на -O1.
При АРМ има значение конвенцията за викане, най-стандартните са доста бавни. Но все си мисля че за инлайн не трябва да има значение това.
|
| Вто Апр 19, 2011 9:19 pm |
|
 |
|
miro_atc
Ранг: Форумен бог
Регистриран на: Нед Фев 26, 2006 6:52 pm Мнения: 11266 Местоположение: Добрич
|
Пирев, много неща не са ми ясни, но 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 9:22 pm |
|
 |
|
¶
Ранг: Форумен бог
Регистриран на: Пет Фев 25, 2005 1:58 pm Мнения: 4585 Местоположение: US
|
Нищо не твърдя по този въпрос
Така, по проблемите:
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, като остане време ще проверя и там какво става.
_________________ Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....
|
| Вто Апр 19, 2011 10:30 pm |
|
 |
|
miro_atc
Ранг: Форумен бог
Регистриран на: Нед Фев 26, 2006 6:52 pm Мнения: 11266 Местоположение: Добрич
|
Значи inline не се inline-ва докато не включиш оптимизациите. Това си е ОК. Важното е да се оптимизира като ги включиш...
btw Без оптимизации GCC генерира много код, ама пък тия 45-47 инструкции сякаш са твърде много. На твое място бих разгледал какво точно прави в пролога и епилога на функцията. При АРМ примерно изключвам фреймирането на стека щото не е необходимо. Много добре си се справя и без него  Другият случай когато бълва много код на всяка функция е ако пишеш Ц++ и ползваш ексепшъни
|
| Сря Апр 20, 2011 8:13 am |
|
 |
|
Реконструктор
Ранг: Форумен бог
Регистриран на: Съб Сеп 25, 2004 12:32 pm Мнения: 8382 Местоположение: София
|
Инлайна не е задължителен.  Компилатора си решава. И в подавящата част от случаите той решава ф-ята да не е инлайн.
В M$ компилатора си има една хубава директива, _forceinline, която забранява на компилатора да мисли и решава и директно третира кода като дефинитивна ф-я.
|
| Сря Апр 20, 2011 11:15 am |
|
 |
|
woody
Ранг: Форумен бог
Регистриран на: Вто Юли 31, 2007 2:55 pm Мнения: 1792 Местоположение: София
|
Пробвах на Микрочипския, както е инсталиран покрай техен кит отпреди време - генерира бозици (постоянен трансфер към/от акумулатора - прилича като на липсваща оптимизация), че и задължителния 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
 |  |  |  | Код: --- C:\Microchip Starter Kits\PIC32 Starter Kits\maddtest\main.c ------------------------------- 1: static long long __attribute__((always_inline)) maddn (long long acc, const int *x, const int *y, int n) 2: { 9D000018 3C021234 lui v0,0x1234 9D00001C 34425678 ori v0,v0,0x5678 9D000020 00001821 addu v1,zero,zero 9D000024 00400013 mtlo v0 9D000028 00600011 mthi v1 9D00002C 3C060001 lui a2,0x1 9D000030 3C050002 lui a1,0x2 3: while (n--) 4: acc += (long long)*x++ * *y++; 9D000034 24040063 addiu a0,zero,99 9D000038 2407FFFF addiu a3,zero,-1 9D00003C 8CC30000 lw v1,0(a2) 9D000040 8CA20000 lw v0,0(a1) 9D000044 2484FFFF addiu a0,a0,-1 9D000048 70620000 madd v1,v0 9D00004C 24A50004 addiu a1,a1,4 9D000050 1487FFFA bne a0,a3,0x9d00003c 9D000054 24C60004 addiu a2,a2,4 5: 6: return acc; 7: } 8: 9: int main (void) 10: { 11: int *x, *y; 12: long long r; 13: 14: x = (int *)0x10000; 15: y = (int *)0x20000; 16: r = maddn (0x12345678, x, y, 100); 17: 18: return r; 19: } 9D000058 03E00008 jr ra 9D00005C 00001012 mflo v0
|  |  |  |  |
|
| Сря Апр 20, 2011 1:18 pm |
|
|
Кой е на линия |
Потребители разглеждащи този форум: 0 регистрирани и 2 госта |
|
Вие не можете да пускате нови теми Вие не можете да отговаряте на теми Вие не можете да променяте собственото си мнение Вие не можете да изтривате собствените си мнения Вие не можете да прикачвате файл
|
|