Отговори на тема  [ 30 мнения ]  Отиди на страница 1, 2  Следваща
GNU C/C++ static inline функция 
Автор Съобщение
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Пет Фев 25, 2005 1:58 pm
Мнения: 4585
Местоположение: US
Мнение 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 и да го
оставя в регистрите ?

_________________
Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....


Вто Апр 19, 2011 3:12 am
Профил WWW
Ранг: Форумен бог
Ранг: Форумен бог

Регистриран на: Нед Фев 26, 2006 6:52 pm
Мнения: 11266
Местоположение: Добрич
Мнение 
Честно казано не разбирам много от нещата дето си написал, но аз бих го направил с 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 9:56 am
Профил
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Съб Сеп 25, 2004 12:32 pm
Мнения: 8382
Местоположение: София
Мнение Re: GNU C/C++ static inline функция
¶ написа:
Та въпроса ми е как да "излекувам" GCC-то, че да не ми "оптимизирва" резултата от madd и да го
оставя в регистрите ?


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


Вто Апр 19, 2011 10:15 am
Профил
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Вто Юли 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
Мнение 
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 пример да да видя с него какво се компилира.

В краен случай ще си напиша една функция на "чист" асемблер ...

_________________
Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....


Вто Апр 19, 2011 4:03 pm
Профил WWW
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Вто Юли 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
Профил WWW
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Вто Юли 31, 2007 2:55 pm
Мнения: 1792
Местоположение: София
Мнение 
Пробва ли с нещо от рода на:

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


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


Вто Апр 19, 2011 5:02 pm
Профил
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Нед Юни 10, 2007 2:22 pm
Мнения: 6492
Местоположение: София
Мнение 
¶ написа:
Template на woody също генерира много код, 44 инструкции. Явно ще трябва да си го направя това парченце код на асемблер в отделен файл ( ако не преборя вградения асемблер де ).


Поне за един компилатор знам, че вграденият му асемблер не е баш асемблер, а правел "еквивалент на".
Това го беше изобретил един тъпанар дето до някое време му отговарях на поствете в CAE, ама то
беше отдавна де. Имам спомен, че и други са го последвали - тоя специално работеше за ARM.

_________________
-------------------
www.tgi-sci.com
-------------------
http://www.flickr.com/photos/didi_tgi/


Вто Апр 19, 2011 6:54 pm
Профил WWW
Ранг: Форумен бог
Ранг: Форумен бог

Регистриран на: Пон Мар 13, 2006 1:59 pm
Мнения: 3867
Местоположение: Габрово
Мнение 
Въобще случва ли се inline-ването или не?

Имаше специален warning на GCC който предупреждава ако по някаква причина функция, дето е обявена за инлайн, не се инлайн-не - дали поради настройки или други причини. Включи го и виж дали няма да изквичи някъде че нещата не са това което очакваш.

Отделно GCC искаше самата функция да е дефинирана и декларирана преди реда, в който я викаш. Иначе казва нещо от сорта "тялото на функцията не е налично". Т.е. тия функции е хубаво да са в хедър, и да са подредени според "dependencies" - първо тази която не зависи от никоя друга и т.н.

Ако вече е инлайната успешно, можеш да видиш разни атрибути на функциите - имаше "naked" и подобни. Ама поне за АРМ не са ми трябвали и компилатора се справя добре дори на -O1.

При АРМ има значение конвенцията за викане, най-стандартните са доста бавни. Но все си мисля че за инлайн не трябва да има значение това.


Вто Апр 19, 2011 9:19 pm
Профил
Ранг: Форумен бог
Ранг: Форумен бог

Регистриран на: Нед Фев 26, 2006 6:52 pm
Мнения: 11266
Местоположение: Добрич
Мнение 
Пирев, много неща не са ми ясни, но 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 9:22 pm
Профил
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Пет Фев 25, 2005 1:58 pm
Мнения: 4585
Местоположение: US
Мнение 
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, като остане време ще проверя и там какво става.

_________________
Ето аз дишам, работя, живея и програми пиша тъй както умея, с проца под вежди се гледаме строго и боря се с него доколкото мога....


Вто Апр 19, 2011 10:30 pm
Профил WWW
Ранг: Форумен бог
Ранг: Форумен бог

Регистриран на: Нед Фев 26, 2006 6:52 pm
Мнения: 11266
Местоположение: Добрич
Мнение 
¶ написа:
връщанео на резултата, с макроса винаги го връща където го очаквам, без значение дали е включена оптимизацията, докато static inline го скрива при включена оптимизация.


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

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


Сря Апр 20, 2011 8:13 am
Профил
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Съб Сеп 25, 2004 12:32 pm
Мнения: 8382
Местоположение: София
Мнение 
gicho написа:
Въобще случва ли се inline-ването или не?


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


Сря Апр 20, 2011 11:15 am
Профил
Ранг: Форумен бог
Ранг: Форумен бог
Аватар

Регистриран на: Вто Юли 31, 2007 2:55 pm
Мнения: 1792
Местоположение: София
Мнение 
¶ написа:
Възможно е 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


Сря Апр 20, 2011 1:18 pm
Профил
Покажи мненията от миналия:  Сортирай по  
Отговори на тема   [ 30 мнения ]  Отиди на страница 1, 2  Следваща

Кой е на линия

Потребители разглеждащи този форум: Google [Bot] и 2 госта


Вие не можете да пускате нови теми
Вие не можете да отговаряте на теми
Вие не можете да променяте собственото си мнение
Вие не можете да изтривате собствените си мнения
Вие не можете да прикачвате файл

Търсене:
Иди на:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by ST Software for PTF.
Хостинг и Домейни