Ползвам 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. Сега в голяма програма където има много на брой цикли с малко итерации, сигурно нещата са различни, не знам.