LINUX.ORG.RU

[криокамера] Насколько хороши сегодняшние оптимизирующие компиляторы?

 


0

0

Когда-то в молодости я увлекался асмом и с тех пор был свято уверен, что на x86 для повтора некоторых действий N ра нет ничего лучше цикла, в котором счетчик уменьшается от N до 0. Сейчас ради прикола попробовал такой «код» скомпилировать:

#include <stdio.h>

int main()
{
  unsigned i;
  for (i=0; i<100; i++) printf("Hello, world!\n");
  return 0;
}

gcc -O4 -S test.c, а там (выравнивание поскипано)

        xorl    %ebx, %ebx
.L2:
        movl    $.LC0, %edi
        addl    $1, %ebx
        call    puts
        cmpl    $100, %ebx
        jne     .L2

Тогда я попробовал

#include <stdio.h>

int main()
{
  unsigned i = 100;
  do {
    printf ("Hello, world!\n");
  } while (--i);
}

Результат получился точно такой же. И только с -Os я получил свой decl %ebx; jne .L2

Собственно, вопрос: почему добавление единички у нас лучше, чем инкремент (это же как минимум длиннее)? И почему второй цикл был «перевернут»? Ведь декремент установит ZF и необходимость в сравнении просто отпадет сама собой.

P. S. Выставил -Os в make.conf, планирую пересобирать мир.

Ответ на: комментарий от anonymous

>По тактам такая операция намного дороже, чем несколько дешевых операций над регистрами.

icc с тобой не согласен. А такты считать немодно, начиная с древнего Pentium Pro: там операции разбивались на микроинструкции, каждая микрооперация выполнялась какое-то время, но определенные микрооперации могли быть выполнены параллельно. Так вот, mov reg,[mem] -> modify reg -> mov [mem],reg не могут выполняться параллельно, т. к. операция над регистром будет ожидать результатов операции загрузки значения в регистр.

Более того, load -> change -> store следует использовать в тех случаях, когда тебе нужен результат. Допустим, если бы я написал while (--i != 1), единственный правильный выход был бы load -> dec -> store -> cmp reg,1. Но у меня специальный случай сранения с нулем и результат операции я нигде не использую. В этом случае dec [mem] будет быстрее, т. к. это одна операция, которая установит все необходимые флаги.

А вот dec [mem], mov reg, [mem], cmd reg, 1 было бы уже отвратительно плохо, т. к. здесь 3 (ТРИ) обращения к памяти вместо двух: два обращения в dec и одно в последующем чтении.

linuxfan
() автор топика
Ответ на: комментарий от Harald

>ты имеешь в виду, что после сохранения декрементированного значения i в память это значение в памяти может произвольно измениться (например, произойдет прерывание, которое изменяет ячейку памяти 20(%rsp))?

Да. Учитывая, что это volatile переменная, ее значение может быть изменено в соседнем треде и, скажем, printf («%d», --i) может дать нам значение, меньшее на 2, чем текущее значение (на самом деле, просто неопределенное значение, поскольку в соседнем треде с volatile-переменной может произойти все, что угодно). По-моему, это все-таки баг.

linuxfan
() автор топика
Ответ на: комментарий от anonymous

>У человечишки мозг сплавится, а компьютеру по фиг.

У тебя — возможно. У меня не плавился ни 5 лет назад, ни сейчас. Это просто как дважды два.

Кстати, было бы интересно услышать, что ты думаешь по поводу ассемблерных вставок, скажем, в кодеках? Их пишут вот такие вот «человечишки», выигрывая у «умного компилятора» в разы.

То, что ты глупее компилятора, видно хотя бы по тому, что ты не можешь понять смысла сделанных им преобразований.

То, что ты глупее наиглупейшего из пары (linuxfan, компилятор) видно уже по твоему благоговейному восхищению сверхъестественной и непостижимой мощью компилятора. И очень смешно видеть, как ты веришь в его непогрешимость.

linuxfan
() автор топика
Ответ на: комментарий от tailgunner

> Ненене, Дэвид Блейн. Кэш не безразмерный, так что оптимизация по размеру имеет смысл.

Оптимизацию по кешу я упомянул. Оптимизация по размеру не приоритетна в большинстве случаев.

anonymous
()
Ответ на: комментарий от linuxfan

> У меня не плавился ни 5 лет назад, ни сейчас. Это просто как дважды два.

И 5 лет назад, и сейчас ты компилятору в подмётки не годишься.

Их пишут вот такие вот «человечишки», выигрывая у «умного компилятора» в разы.

Потому как используют неизвестные компилятору фичи (SIMD-ы те же).

восхищению сверхъестественной и непостижимой мощью компилятора

Я сам пишу компиляторы. И знаю, что человек так мыслить не способен.

anonymous
()
Ответ на: комментарий от anonymous

>Потому как используют неизвестные компилятору фичи (SIMD-ы те же).

Дитятко, открой для себя чудесные компиляторы C и Fortran от Intel. Они тебя такому SSE обучат, о котором ты и не мечтал.

И 5 лет назад, и сейчас ты компилятору в подмётки не годишься.

FYI как раз пять лет назад я писал memcpy, стабильно обгоняющий библиотечный, начиная с некоторого размера блока. Скажем спасибо SSE-инструкциям, которые могут читать/писать данные, не загрязняя кеш.

Я сам пишу компиляторы. И знаю, что человек так мыслить не способен.

Да у нас тут каждый первый если не компиляторы пишут, то процессоры паяют. Да такие, что рвут топовые i7 как тузик грелку. Жаль только секретные насовские разработки NDA продемонстрировать мешает :D

Кстати, открой для себя, что человек мыслит не перебором.

linuxfan
() автор топика
Ответ на: комментарий от anonymous

>Оптимизация по размеру не приоритетна в большинстве случаев.

Че, subq $1,%rax уже стал лучше decq %rax? А я-то думал, что они отличаются как минимум на 8 байт, которые еще прочитать надо.

linuxfan
() автор топика
Ответ на: комментарий от linuxfan

> Дитятко, открой для себя чудесные компиляторы C и Fortran от Intel. Они тебя такому SSE обучат, о котором ты и не мечтал.

Сявка, я SIMD-ы использовал, когда интеловские процессоры были ещё 486 и ни о каких SIMD интел тогда и не мечтал. И то, что делает интеловский компилятор - это пока ещё примитив. Что и не удивительно, C++ - не тот язык, который легко анализировать и выявлять возможности использования SIMD. Даже с Fortran это проще выходит.

Кстати, открой для себя, что человек мыслит не перебором.

Открой для себя, что компиляторы пользуются хитрыми эвристиками для раскраски графов и для instruction scheduling. Человеку лет 10 потребуется, чтоб на интуитивном уровне этим эвристикам научиться, как шахматному гроссмейстеру. К этому времени процессоры уже совсем другими будут, и все эти знания станут бесполезны (как у тебя, с твоей нежной любовью к decl).

anonymous
()
Ответ на: комментарий от anonymous

>как у тебя, с твоей нежной любовью к decl

Так какие компиляторы ты пишешь? Очевидно, что не icc, т. к. icc как раз использует dec, тем самым как бы подтверждая, что мои древние познания асма до сих пор более чем актуальны.

Даже с Fortran это проще выходит.

Лол, и чем же фортран проще C?

linuxfan
() автор топика
Ответ на: комментарий от anonymous

>Не, ну разве не идиот?

Иными словами, ты не смог постичь того, что открылось мне за 10 минут беглого просмотра Intel® 64 and IA-32 Architectures Optimization Reference Manual? Я начинаю опасаться, что ты сабмитишь свой код в gcc.

linuxfan
() автор топика
Ответ на: комментарий от AntonK

>Да ему тут уже человек пять объяснить пытались, что он неправ. Но с ним спорить бесполезно...

Причем каждый говорил одно и то же: мне кореш говорил, что современные конпеляторы такие крутые, что человек им сливает. Ни один так и не привел ссылки на официальную документацию Intel.

linuxfan
() автор топика
Ответ на: комментарий от AntonK

>А ты привел?

Вывод icc. Изначально я вообще-то задал вопрос, но получил в ответ неаргументированное «конпелятор все равно умнее».

linuxfan
() автор топика
Ответ на: комментарий от linuxfan

По-моему вы не совсем правы. Если смотреть реальный код в gdb, а не asm листинг, то для приведённого в нулевом посте(второй случай), у меня следующее(-O1):

0x80484b0 <main>:       lea    0x4(%esp),%ecx
0x80484b4 <main+4>:     and    $0xfffffff0,%esp
0x80484b7 <main+7>:     pushl  -0x4(%ecx)
0x80484ba <main+10>:    push   %ebp
0x80484bb <main+11>:    mov    %esp,%ebp
0x80484bd <main+13>:    push   %ebx
0x80484be <main+14>:    push   %ecx
0x80484bf <main+15>:    sub    $0x10,%esp
0x80484c2 <main+18>:    mov    $0x64,%ebx
0x80484c7 <main+23>:    movl   $0x80485ac,(%esp)
0x80484ce <main+30>:    call   0x80483d0 <puts@plt>
0x80484d3 <main+35>:    sub    $0x1,%ebx
0x80484d6 <main+38>:    jne    0x80484c7 <main+23>
0x80484d8 <main+40>:    mov    $0x0,%eax
0x80484dd <main+45>:    add    $0x10,%esp
0x80484e0 <main+48>:    pop    %ecx
0x80484e1 <main+49>:    pop    %ebx
0x80484e2 <main+50>:    pop    %ebp
0x80484e3 <main+51>:    lea    -0x4(%ecx),%esp
0x80484e6 <main+54>:    ret

Возможно не идеал, но вполне нормально. Потом зачем -O4? Ведь больше 3, это всё равно 3. И вообще -O3 не рекомендуется использовать в gcc 4, а рекомендуется -O2.

Booster ★★
()
Ответ на: комментарий от linuxfan

http://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2009-January/0005...

http://www.intel.com/Assets/PDF/manual/248966.pdf

3.5.1.1 Use of the INC and DEC Instructions

The INC and DEC instructions modify only a subset of the bits in the flag register. This creates a dependence on all previous writes of the flag register. This is especially problematic when these instructions are on the critical path because they are used to change an address for a load on which many other instructions depend.

Assembly/Compiler Coding Rule 32. (M impact, H generality) INC and DEC instructions should be replaced with ADD or SUB instructions, because ADD and SUB overwrite all flags, whereas INC and DEC do not, therefore creating false dependencies on earlier instructions that set the flags.

linuxfan, ты не прав. Современные микропроцессоры очень сложны, и оптимизация под них может приводить к совершенно не очевидному на первый взгляд коду. К непонятным пересылкам между регистрами или памятью, которые например выльются в лучший параллелизм и т.д.

Eshkin_kot ★★
()
Ответ на: комментарий от linuxfan

То что генерится по -O3 я тоже не вкуриваю. Пишут, что этот флаг может приводить к багам/неверной работе программы, так что лучше оставить его в покое.

Booster ★★
()
Ответ на: комментарий от linuxfan

И кстати по-размеру лучше не оптимизировать. Яркий тому пример - «loop», компактно, но оооооочень большой тормоз.

Booster ★★
()
Ответ на: комментарий от linuxfan

Идиот. Клинический. icc не авторитет. Любой тупейший бенчмарк был бы авторитетнее в этом споре.

anonymous
()
Ответ на: комментарий от anonymous

>icc не авторитет

Ты определись уже, а то у тебя то конпелятор бох, человек аццтой, то наоборот.

linuxfan
() автор топика

К стыду своему ничего здесь вообще понять не могу.

.L2:
        mr 3,30
        bl puts
        cmpwi 7,31,99
        addi 31,31,1
        bne 7,.L2
        lwz 0,20(1)
        lwz 30,8(1)
        li 3,0
        lwz 31,12(1)
        addi 1,1,16
        mtlr 0
        blr

goose
()

Первый компьютер у меня был спектрум, и писать сколько нибудь сложные программы на ассемблере было в порядке вещей. Да и по сути в те времена никаких альтернатив для него не было - басик и ассемблер. Поэтому я писал и на том и на том.

Позже в 91 когда мне достался 386 комп, я стал осваивать паскаль и си, ну и x86 асм. Мне чертовски не нравилось, что на моем уже не слишком новом компе тормозят игры и я клял разработчиков, что они поленились писать на ассемблере, это ведь так Ъ. Ну и что бы не уподобляться им, в своих прогах я писал почти все что мог на ассемблере. Апофеозом этого ассмеблерофильства, стали стандартные сишные рантайм библиотеки переписаные на ассемблере. Мне грело душу, что с ними проги работают на 5-15% быстрее.

В 98 когда я начал работать пришлось писать для процессора TMS320 (серию уже не помню, но не суть), и все мое доскональное знание ассмеблера x86 пошло в топку, вместе с рантайм библиотеками. Параллелизм операций + 100500 регистров давались с трудом...

Это слегка пошатнуло мою любовь к ассемблеру, но все же всякие куски кода продолжал писать на нем, и опять же, правда в этот раз частично, переписал сишные рантайм библиотеки.

Потом последовала смена платформы... И еще дважды.

Сейчас я стал старым и мудрым, и не пишу на ассеблере.

CFA
()

У писателей оптимизаторов и кодогенераторов есть два вида деятельности. Первый — разработка оптимизаций, которые ускоряют много существующих программ. Второй — разработка преобразований, после которых код начинает выглядеть лучше с точки зрения опытных программистов на ассемблере. К сожалению, часто судят по внешнему виду, т.е. по результатам преобразований второго рода. А реальные плоды приносят, в основном, преобразования первого рода.

anonymous
()
Ответ на: комментарий от linuxfan

> >Не, ну разве не идиот?

Идиот-идиот.

Иными словами, ты не смог постичь того, что открылось мне за 10 минут беглого просмотра

Ух ты, за 10 минут осилил мануал и разораблся в деталях? Не верю. Ты не только идиот, но ещё и любитель похвастаться.

Я (другой анонимус) никак не могу понять, каким принципом ты рукводствуешься для оценки «скорости» кода?

Где тесты? Где результаты? Может хватить сношать окружающим мозг и считать «абстрактную» эффективность на основе поверхностных и дилетантских знаний?

anonymous
()
Ответ на: комментарий от anonymous

По-вашему инкремент и сравнение со значением, лучше чем один декремент? По-моему -O3 действительно странно себя ведёт.

Booster ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.