Автор Тема: gcc, -O3 vs -Ofast vs -Os  (Прочетена 15795 пъти)

gat3way

  • Напреднали
  • *****
  • Публикации: 6050
  • Relentless troll
    • Профил
    • WWW
gcc, -O3 vs -Ofast vs -Os
« -: Oct 27, 2011, 01:22 »
Преди малко бях много изненадан да установя, че код, който аз съм писал, считам че познавам достатъчно добре, при компилация с -Os работи по-бързо, отколкото с -O2 и няколко агресивни оптимизации като -funroll-loops, -minline-all-strings, -fomit-frame-pointer и т.н. Benchmark-ването е лесно понеже програмата само по себе си има функционалност да benchmark-ва скоростта на работа. Реших да си направя още няколко теста и се оказа, че и -O3 и новия -Ofast са по-бавни.

Това не е много странно разбира се, но аз бях убеден, че не трябва да е така - прекалено много разчитам на разни цикли с брой итерации, известни при compile time, прекалено много разчитам на функциите за работа с низове, имам доста малки и рядко викани функции, които би било добре да се inline-ват и цялостният резултат би следвало да е по-добър с -O2 и въпросните агресивни опции или -O3. Всъщност и от -Ofast, там очевидно допълнителните оптимизации са свързани предимно с разни операции с плаваща запетая, които не ме касаят. Иначе -flto не ползвам, защото увеличава времето за компилация безумно много и защото не води при добри резултати при кода, който аз съм писал :) Причината е че имам много лошия навик да клонирам малки парчета код между различни C файлове или да си дефинирам разни много дебели макроси из хедър файловете и смисълът от inline-ването на функции между различните сорс файлове почва да се губи. Това е някакъв остатък отпреди gcc4.5 и е много лош навик, щото води до трудна за подържане кочина, въпреки което продължавам да го правя.

Както и да е, при все това, -Os генерира малко по-бърз код. Очевидно тормоза върху L1 instruction кеша е бил достатъчно голям. А изпълнимото binary не е чак толкова голямо, около 10MB, вероятно като зареди и shared библиотеките се раздува, но не считах че е чак такъв проблем. Отделно агресивното inline-ване и unroll-ване на цикли от страна на компилатора когато съм компилирал въпросните библиотеки (някои от тях аз съм си ги компилирал от сорс вместо да ги инсталирам от пакетната система) раздува кода още повече.

Колкото и да е странно. Предполагам тук има хора, които ползват source-базирани дистрибуции и обичат да си набиват разни неща в CFLAGS или пък просто обичат да си компилират нещата от сорс и да експериментират с флаговете за оптимизация. Чудно ми е забелязвали ли сте подобно поведение при други софтуери и ако да - при кои :)

Знам че темата е такава че има прекалено много неизвестни в уравнението и е много глупаво да се правят обобщения, но все пак ще ми е интересно да чуя за такива случаи.
« Последна редакция: Oct 27, 2011, 01:41 от gat3way »
Активен

"Knowledge is power" - France is Bacon

arda_kj

  • Напреднали
  • *****
  • Публикации: 631
  • Distribution: Debian Sid/Unstable; Ubuntu 12.04
  • Window Manager: Gnome/KDE
    • Профил
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #1 -: Oct 27, 2011, 12:29 »
Дай малко повече инфо коя версия на gcc ползваш, кво става ако компилираш с друга версия или версии. Колко ти е голям кеша? Какъв процесор ползваш и с колко ядра? Многонишково ли е приложението, което стартираш? Какви точно опции набиваш при компилацията в различните случаи?

От това, което си споменал аз мога да заключа, че при използване на -O2/-O3, това което се изпълнява в даден момент не се побира в кеша и затова производителността ти пада. Имай предвид, че всички оптимизации малко или много увеличават размера на компилирания код и ако кеша ти е не е достатъчен вместо ускоряване получаваш деградация на производителността щото трябва да се чете от RAM, а това си е бавен процес.

ПС: Тука е мястото да споделя, че една програма, която съм писал на C при компилация с gcc 4.4 дава 40% по-бърз код отколкото при компилация с gcc 4.5/4.6. Понеже тази програма ми е важна си седя и си я компилирам с gcc 4.4. Не мога да си обясня тази деградация на производителността при положение, че набивам едни и същи опции за оптимизация на gcc-то.
Активен

Debian Sid/Unstable; Ubuntu 12.04
"За да открием истината, е нужно поне веднъж в живота си да подложим всичко на съмнение" - Р. Декарт

ray

  • Напреднали
  • *****
  • Публикации: 1447
    • Профил
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #2 -: Oct 27, 2011, 12:58 »
Здравейте,

При сорс-базирани дистрибуции (първо Gentoo, после Exherbo и сега Funtoo) от доста време ползвам -Os, всичко (99 %) се компилира и работи, но не съм тествал спрямо -О2/3.
Тази информация е по-скоро да укаже че няма проблеми с този флаг отколкото да даде някакви сравнителни данни.
Дано информацията е полезна.
Румен
Активен

n00b

  • Напреднали
  • *****
  • Публикации: 1248
  • Distribution: OSX
  • Window Manager: 10.6, 10.8, 10.9
  • Live to hack, hack to live.
    • Профил
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #3 -: Oct 27, 2011, 16:12 »
От дълго време компилирам само с -Os точно заради този ефект.

Все пак новите процесори имат branch predicting и всякакви неща като unroll loops само товарят повече програмата като размер, но имат негативно отношение точно заради L1.
Активен

mobilio - професионални мобилни приложения

gat3way

  • Напреднали
  • *****
  • Публикации: 6050
  • Relentless troll
    • Профил
    • WWW
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #4 -: Oct 27, 2011, 23:53 »
Ползвам 4.6 и съм компилирал и с 4.4 и 4.5. Процесорът е 4-ядрен, Phenom2, с 6MB споделена L3, 4*512KB L2 и 4*64*2 KB L1. Приложението е многонишково. Опциите които набивам някъде по-горе ги споделих.

Dataset-а винаги е прекалено голям, за да се побере в кеша и това е много условно понятие апропо. В дадени случаи може да се каже, че това, върху което вървят голяма част от операциите може да се побере в кеша, а големите помощни таблици се достъпват толкова рядко, че това няма значение.

Моментът със срива на производителността от 4.4 нататък го знам, но след един ъпдейт на 4.6 от хранилищата на дебиан, нещата с 4.6 се върнаха на нивата от 4.4.3.

Между другото днес доста размишлявах по тези проблеми и реших да изхвърля тотално funroll-loops и да мина на ръчно unroll-ване на цикли където е възможно. Това е понеже компилатора в доста случаи няма как да вземе решение, а когато взема решението, unroll-ва прекалено брутално. Имам предвид случаи като тези:

Код:
for (a=0;a<limit;a++)
{
    array[a]=something;
}

Ако limit не е известен при compile time, този цикъл не може да бъде unroll-нат. Понеже имам доста такива случаи, за които знам със сигурност, че limit ще е кратно на 2, ръчно си го unroll-вам. Резултатът определено се усеща.

Покрай това направих няколко оптимизации, преместих няколко неща от глобални променливи в TLS-а, защото потенциално създаваха възможност за false sharing, редуцирах си overhead-а от някои function call-ове като им премахнах един аргумент (създавайки няколко версии на функциите, защото аргумента може да има стойност 0 или 1, така също се спестява branch-ване).

Производителността ми скочи с близо 20% и сега отново -O2 -minline-all-stringops -fomit-frame-pointer е по-бърз от -Os, не с много де.

Апропо, поиграх си и с опциите, които gcc дава, за да помогнем на компилатора да оптимизира branch prediction-a, демек __builtin_expect. Резултатът не беше особено велик.

Между другото, branch prediction-а е нещо, коетo което много лесно може да бъде счупено. Един прост пример, ако искаме да преброим броя на нечетните елементи в масив с дължина за която знаем че е четна:

Код:
int odds=0;
for (a=0;a<max;a++)
{
   if (array[a]%2) odds++;
}

Такива неща е*ават майката на всичко хубаво, което процесорните производители са направили за теб.
Дори да заместим modulo операцията с bitwise такава (което компилатора така или иначе вероятно прави когато генерира оптимизиран код). В този случай има огромна полза да unroll-неш цикъла  ей така:


Код:
odds;
for (a=0;a<max;a+=2)
{
   odds+=(array[a]&1);
   odds+=(array[a+1]&1);
}


Това е зъл пример де, но дори в общият случай, ако можем да unroll-нем цикъла примерно 2 пъти и си съкратим 2 пъти CMP инструкциите, според мен файдата от спестените CMP инструкции далеч надминава вредите от разхитените няколко десетки байта instruction cache. Сега в голяма програма където има много на брой цикли с малко итерации, сигурно нещата са различни, не знам.
« Последна редакция: Oct 28, 2011, 00:44 от gat3way »
Активен

"Knowledge is power" - France is Bacon

Naka

  • Напреднали
  • *****
  • Публикации: 3395
    • Профил
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #5 -: Oct 28, 2011, 14:03 »
-Ofast явно e много гадна опция защото включва в себе си и -ffast-math.

Досега много пъти съм се пробвал да компилирам големи програми (от реда на редактори, офис програми, игри) с -ffast-math и в 90% от случаите прогамите се крашват, нетръгват или работят неправилно.

Бягайте далече от -ffast-math

Активен

Perl - the only language that looks the same before and after encryption.

gat3way

  • Напреднали
  • *****
  • Публикации: 6050
  • Relentless troll
    • Профил
    • WWW
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #6 -: Oct 28, 2011, 22:17 »
Хм, profile-нах програмата с cachegrind, оказва се че L1/L2 miss ratio-то е относително ниско, около 2% (вярвах че е доста по-зле). Също така, branch misprediction-а се движи около 5-6%. Много полезен плъгин на valgrind апропо. Обаче получените данни така и не обясняват горния резултат, хмммм...
Активен

"Knowledge is power" - France is Bacon

arda_kj

  • Напреднали
  • *****
  • Публикации: 631
  • Distribution: Debian Sid/Unstable; Ubuntu 12.04
  • Window Manager: Gnome/KDE
    • Профил
Re: gcc, -O3 vs -Ofast vs -Os
« Отговор #7 -: Oct 29, 2011, 01:56 »
За съжаление нямам много време, иначе темата която си зачекнал е доста интересна. Живо ме интересува всичко свързано с оптимизации на програмен код.

Сега по темата:
1) Ако ползваш многонишково приложение, сигурен ли си, че mutex-ите не скапват производителността. Може би има някакви забавяния при lock и/или unlock на mutex-а особено ако е в някакъв цикъл.
2) Пробвай да намалиш размера на данните, които евентуално ще влизат в кеша и виж какво ще стане с производителността (примерно размера на разните масиви, които ползваш, така че целия да се побера в кеша).
3) Погледни disassembeled версия на програмата да видиш какви инструкции генерира gcc в различните случаи. Може би латенсито на кода генериран с -O2/O3 да е по-голям по някаква причина. Аз при мен набивам следното нещо на gcc и ми вади най-бърз код за сега от всички експерименти:

Код:
-O3 -fomit-frame-pointer -funroll-loops -msse3 -mfpmath=sse -mcx16 -msahf --param l1-cache-size=32 --param l1-cache-line-size=64 --param l2-cache-size=2048 -march=core2

За АМД предполагам кеша и процесора са различни, така че си ги настрой според твоя случай.
4) Погледни следния линк, някакъв пич дето е бачкал в АМД обяснява разни мемори оптимизации, със сигурност ще ти е интересно:
http://blogs.utexas.edu/jdm4372/2010/11/

Аз съм с core2 на Интел, но някои от предложенията спокойно си бачкаха и на core2.
Активен

Debian Sid/Unstable; Ubuntu 12.04
"За да открием истината, е нужно поне веднъж в живота си да подложим всичко на съмнение" - Р. Декарт