Микроконтролери и електроника
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:

Код:
static __inline int64 MADD64(int64 sum, int x, int y)
{
   return (sum + ((int64)x * y));
}


Така написана обаче генерира твърде много инструкции, на всичкото отгоре
въпреки 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 е помощно обединение дефинирано така:

Код:
typedef union tagInt64 {
   int64   n;
   struct {
      uint32   lo;      
      int32   hi;                  
   };
}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 функция чието съдържание е на асемблер.
Слагаш я в хедър също като макроса, но е по-удобна за дебъгване (при изключени оптимизации естествено). При свързване пък функцията ти дава малко повече свобода с параметрите.

Един бърз преглед в нета дава това:

Код:
typedef union _U64
{
   long long w64;
   struct{
      unsigned int lo32;
      signed int hi32;
   } r;
} U64;
static __inline long long MADD64(long long sum64, int x, int y)
{
   U64 u;
   u.w64 = sum64;
   __asm__ volatile ("smlal %0,%1,%2,%3" : "+&r" (u.r.lo32), "+&r" (u.r.hi32) : "r" (x), "r" (y) : "cc");
   return u.w64;
}


Сега това е за АРМ... и в интерес на истината аз го правя по друг начин. Вместо "+" за 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 функция

¶ написа:
Та въпроса ми е как да "излекувам" GCC-то, че да не ми "оптимизирва" резултата от madd и да го
оставя в регистрите ?


Ами скромният ми опит е с AVR, естествено. :) Единственият начин, по който успях да накарам gcc да не пипа дадени регистри, беше с дефиниране на глобални променливи като register volatile. Наистина не ги пипа, но регистрите, които си посочил, стават неизползваеми за компилатора.

Автор:  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 ]
Заглавие: 

miro_atc написа:
Честно казано не разбирам много от нещата дето си написал, но аз бих го направил с inline static функция чието съдържание е на асемблер.


Проблема е в генерирания код. Ето как изглежда като static inline:

Код:
static __inline int64 MADD64(int64 s, int x, int y) {
Int64 t;
   t.n = s;   
   __asm__ volatile ("madd %2,%3"  : "+l" ( t.lo ), "+h" ( t.hi ) : "%r" (x), "r" (y));
   return t.n;
}



Но така, при включени оптмизации, static online ми генерира 47 инструкции, докато макроса само 17, a MADD64 се вика около 200 хил. пъти в секунда. Разликата е повече от 50% в бързодействието. Не че не ми е по-удобно да използвам static inline, но много код отива там.

По принцип на мен въобще не ми е ясно как се работи с вградения асемблер, добавят се едни параметри дето от разни примери се опитвам да разгадая какво вършат. Например "+l" и "+h" указват къде да се съхрани резултата, MIPS32 ядрото има 2 регистъра, hi & lo, които се използват за натрупване на резултата от MADD. А "%r" въобще не знам какво значи :( , гледах някакъв пример, че го използват и аз така го направих, че и работи на всичкото отгоре :-) Къде мога да прочета всичките тези "=r","%r","+&r" и т.н. как се интерпретират ?


Реконструктор, не мога да си позволя да заделя глобално регистри само заради тази операция.


MADD инструкцията се вика лесно, примерно ето една последователност на чист асемблер ( не е вградения имам в предвид под "чист" ):
Код:
; [v1:v0] += a0 * a1;
mtlo v0 ; копирай v0 в lo
mthi v1 ; копирай v1 в hi
madd a0,a1 ; [hi:lo] += a0 * a1;
mflo v0 ; копирай lo в v0
mfhi v1 ; копирай hi в v1
; резултата от MADD е в [v1:v0]


Ще пробвам по-късно посочения от 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 ]
Заглавие: 

Пробва ли с нещо от рода на:

Код:
static long long __attribute__((always_inline)) maddn (const int *x, const int *y, int n)
{
    long long acc = 0;

    while (n--)
        acc += (long long)*x++ * *y++;

    return acc;
}


Т.е. да не караш всеки път да пренася акумулатора през регистри, ами да си трупа в него.

Автор:  tgi [ Вто Апр 19, 2011 6:54 pm ]
Заглавие: 

¶ написа:
Template на woody също генерира много код, 44 инструкции. Явно ще трябва да си го направя това парченце код на асемблер в отделен файл ( ако не преборя вградения асемблер де ).


Поне за един компилатор знам, че вграденият му асемблер не е баш асемблер, а правел "еквивалент на".
Това го беше изобретил един тъпанар дето до някое време му отговарях на поствете в 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 функции - тогава може да използва други по-удобни регистри вместо стандартните. Но не знам дали би вкарал и акумулатора в тая игра. Честно казано ме съмнява...

Що се отнася до асемблера там нещата са прости - имаш:

Код:
asm volatile (
   "асемблерски инструкции"
     : списък с изходни регистри (outputs)
     : списък с входни регистри и/или параметри (inputs)
     : списък с нещата дето се омазват (globbers)
);


Елементите от 3-та списъка получават номера (от нула) и ако искаш да ползваш даден елемент в асемблера го ползваш с %номерче. Има го в нета да не обяснявам всички.

Според мен така трябва да изглежда твоя код

Код:
asm (
   "madd %4,%5" 

     : "=l" ( out.lo ), "=h" ( out.hi )
     : "0" (in.lo), "1" (in.hi), "r" (x), "r" (y)
    
);

Където 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 ]
Заглавие: 

miro_atc написа:
Пирев, много неща не са ми ясни, но GCC-то не е сред тези неща ;-)


Нищо не твърдя по този въпрос :)

Така, по проблемите:

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 ]
Заглавие: 

¶ написа:
връщанео на резултата, с макроса винаги го връща където го очаквам, без значение дали е включена оптимизацията, докато static inline го скрива при включена оптимизация.


Значи inline не се inline-ва докато не включиш оптимизациите. Това си е ОК. Важното е да се оптимизира като ги включиш...

btw Без оптимизации GCC генерира много код, ама пък тия 45-47 инструкции сякаш са твърде много. На твое място бих разгледал какво точно прави в пролога и епилога на функцията. При АРМ примерно изключвам фреймирането на стека щото не е необходимо. Много добре си се справя и без него ;-) Другият случай когато бълва много код на всяка функция е ако пишеш Ц++ и ползваш ексепшъни

Автор:  Реконструктор [ Сря Апр 20, 2011 11:15 am ]
Заглавие: 

gicho написа:
Въобще случва ли се inline-ването или не?


Инлайна не е задължителен. :) Компилатора си решава. И в подавящата част от случаите той решава ф-ята да не е инлайн.
В M$ компилатора си има една хубава директива, _forceinline, която забранява на компилатора да мисли и решава и директно третира кода като дефинитивна ф-я.

Автор:  woody [ Сря Апр 20, 2011 1:18 pm ]
Заглавие: 

¶ написа:
Възможно е GCC компилатора на Microchip да е поскопен повече отколкото трябва, имам работеща версия на Macragior GCC за MIPS, като остане време ще проверя и там какво става.

Пробвах на Микрочипския, както е инсталиран покрай техен кит отпреди време - генерира бозици (постоянен трансфер към/от акумулатора - прилича като на липсваща оптимизация), че и задължителния 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

:D


EDIT: -O3 -fomit-frame-pointer

Код:
static long long __attribute__((always_inline)) maddn (long long acc, const int *x, const int *y, int n)
{
   while (n--)
      acc += (long long)*x++ * *y++;

   return acc;
}

int main (void)
{
   int *x, *y;
   long long r;

   x = (int *)0x10000;
   y = (int *)0x20000;
   r = maddn (0x12345678, x, y, 100);
   
return r;
}


Код:
---  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

Страница 1 от 2 Часовете са според зоната UTC + 2 часа [ DST ]
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/