Почему компиляторы настолько глупы?

Я всегда удивляюсь, почему компиляторы не могут понять простые вещи, которые очевидны для человеческого глаза. Они делают много простых оптимизаций, но никогда не являются чем-то даже немного сложным. Например, этот код занимает около 6 секунд на моем компьютере, чтобы напечатать нулевое значение (используя java 1.6):

int x = 0; for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) { x += x + x + x + x + x; } System.out.println(x); 

Совершенно очевидно, что x никогда не изменяется, поэтому, независимо от того, как часто вы добавляете 0, он остается равным нулю. Поэтому компилятор мог теоретически заменить это на System.out.println (0).

Или еще лучше, это занимает 23 секунды:

 public int slow() { String s = "x"; for (int i = 0; i < 100000; ++i) { s += "x"; } return 10; } 

Сначала компилятор мог заметить, что я на самом деле создаю строку s 100000 «x», чтобы она могла автоматически использовать s StringBuilder вместо этого или даже лучше сразу заменить ее на итоговую строку, поскольку она всегда одна и та же. Во-вторых, он не признает, что я вообще не использую эту строку, поэтому весь цикл можно отбросить!

Почему, после того, как многие трудовые ресурсы входят в быстрые компиляторы, они все еще настолько онемели?

EDIT : Конечно, это глупые примеры, которые никогда нельзя использовать нигде. Но всякий раз, когда мне приходится переписывать красивый и очень читаемый код во что-то нечитаемое, чтобы компилятор был счастлив и быстро создавал код, мне интересно, почему компиляторы или какой-либо другой автоматизированный инструмент не могут это сделать для меня.

О, я не знаю. Иногда компиляторы довольно умны. Рассмотрим следующую программу C:

 #include  /* printf() */ int factorial(int n) { return n == 0 ? 1 : n * factorial(n - 1); } int main() { int n = 10; printf("factorial(%d) = %d\n", n, factorial(n)); return 0; } 

В моей версии GCC (4.3.2 при тестировании Debian ) при компиляции без оптимизаций или -O1 он генерирует код для factorial (), как и следовало ожидать, используя рекурсивный вызов для вычисления значения. Но на -O2 он делает что-то интересное: он компилируется до жесткой петли:

  factorial: .LFB13: testl %edi, %edi movl $1, %eax je .L3 .p2align 4,,10 .p2align 3 .L4: imull %edi, %eax subl $1, %edi jne .L4 .L3: rep ret 

Довольно внушительный. Рекурсивный вызов (даже хвостовой рекурсивный) полностью исключен, поэтому факториал теперь использует O (1) пространство стека вместо O (N). И хотя у меня есть только очень поверхностное знание сборки x86 (на самом деле AMD64 в этом случае, но я не думаю, что какие-либо расширения AMD64 используются выше), я сомневаюсь, что вы могли бы написать лучшую версию вручную. Но то, что действительно взорвало мой разум, было кодом, который он генерировал на -O3. Реализация факториала осталась прежней. Но main () изменено:

  main: .LFB14: subq $8, %rsp .LCFI0: movl $3628800, %edx movl $10, %esi movl $.LC0, %edi xorl %eax, %eax call printf xorl %eax, %eax addq $8, %rsp ret 

См. movl $3628800, %edx ? gcc является предварительным вычислением факториала (10) во время компиляции. Он даже не вызывает factorial (). Невероятный. Моя шляпа отправляется в команду разработчиков GCC.

Конечно, все обычные заявления об отказе применяются, это всего лишь пример игрушки, преждевременная оптимизация – корень всех злых и т. Д. И т. Д., Но это иллюстрирует, что компиляторы часто умнее, чем вы думаете. Если вы думаете, что можете сделать лучшую работу вручную, вы почти наверняка ошибаетесь.

(Адаптировано из публикации в моем блоге .)

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

 x = 0 sleep 6 // Let's assume this is defined somewhere. print x 

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

Код и компилятор, который его обрабатывает, являются инструментами, и вам нужно быть кузнецом, если вы хотите эффективно использовать их. Сколько 12 «бензопилы откажутся попробовать срубить 30-дюймовое дерево? Сколько упражнений автоматически переключится на режим молота, если они обнаружат бетонную стену?

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

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

Например, я сделал таймерные петли во встроенных системах, где тактовая частота процессора известна точно, но нет надежного устройства синхронизации. В этом случае вы можете точно рассчитать, как долго этот цикл будет принимать и использовать это, чтобы контролировать, как часто происходит ситуация. Это не сработало бы, если бы компилятор (или сборщик в этом случае) решил, что мой цикл бесполезен и оптимизирован из его существования.

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

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

Говоря с точки зрения C / C ++:

Ваш первый пример будет оптимизирован большинством компиляторов. Если java-компилятор из Sun действительно выполняет этот цикл, это ошибка компиляторов, но, честное слово, любой пост 1990 C, C ++ или Fortran-компилятор полностью исключает такой цикл.

Второй пример не может быть оптимизирован на большинстве языков, поскольку распределение памяти происходит как побочный эффект объединения строк вместе. Если компилятор будет оптимизировать код, шаблон распределения памяти изменится, и это может привести к эффектам, которые пытается избежать программист. Деfragmentация памяти и связанные с ней проблемы – это проблемы, с которыми программисты сталкиваются каждый день.

В целом я доволен оптимизаторами, которые могут делать компиляторы в эти дни.

Компиляторы рассчитаны на предсказуемость . Это может заставить их выглядеть глупыми время от времени, но это нормально. Цели автора компилятора:

  • Вы должны иметь возможность посмотреть на свой код и сделать разумные outlookы относительно его производительности.

  • Небольшие изменения в коде не должны приводить к резким различиям в производительности.

  • Если небольшое изменение обращается к программисту, как это должно улучшить производительность, оно должно по крайней мере не ухудшать производительность (если в аппаратном обеспечении не происходит ничего удивительного).

Все эти критерии защищают от «волшебных» оптимизаций, которые применяются только к угловым случаям.


Оба ваших примера имеют переменную, обновленную в цикле, но не используемую в другом месте . Этот случай на самом деле довольно сложно подобрать, если вы не используете какую-то структуру, которая может сочетать устранение мертвого кода с другими оптимизациями, такими как распространение текста или постоянное распространение. Для простого оптимизатора streamа данных переменная не выглядит мертвой. Чтобы понять, почему эта проблема сложная, см. Статью Лернера, Гроув и Чамберс в POPL 2002 , которая использует этот самый пример и объясняет, почему это сложно.

Компилятор HotSpot JIT будет оптимизировать только код, который был запущен в течение некоторого времени. К моменту, когда ваш код горячий, цикл уже запущен, и компилятор JIT должен ждать, пока в следующий раз метод будет введен для поиска путей оптимизации цикла. Если вы вызовете метод несколько раз, вы можете увидеть более высокую производительность.

Это описано в разделе « Часто задаваемые вопросы по HotSpot» в разделе «Я пишу простой цикл во время простой операции, и он медленный. Что я делаю неправильно?».

Шутки в сторону? Зачем кому-либо писать такой код? ИМХО, код, а не компилятор, здесь является «глупым» сущностью. Я абсолютно счастлив, что авторы компиляторов не беспокоятся о том, чтобы тратить время на то, чтобы оптимизировать что-то подобное.

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

Ну, я могу говорить только о C ++, потому что я начинающий Java полностью. В C ++ компиляторы могут игнорировать любые требования к языку, установленные Стандартом, если наблюдаемое поведение является таким, как если бы компилятор действительно эмулировал все правила, которые устанавливаются Стандартом. Наблюдаемое поведение определяется как любое чтение и запись в изменчивые данные и вызовы функций библиотеки . Учти это:

 extern int x; // defined elsewhere for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) { x += x + x + x + x + x; } return x; 

Компилятору C ++ разрешено оптимизировать этот fragment кода и просто добавить правильное значение в x, которое было бы результатом этого цикла один раз, потому что код ведет себя как-если бы цикл никогда не происходил, и не волатильные данные, а также функции библиотеки что может вызвать побочные эффекты. Теперь рассмотрим изменчивые переменные:

 extern volatile int x; // defined elsewhere for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) { x += x + x + x + x + x; } return x; 

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


Говоря о Java, я проверил ваш цикл, и случается, что GNU Java Compiler ( gcj ) занимает слишком много времени, чтобы закончить цикл (он просто не закончил, и я его убил). Я включил флаги оптимизации (-O2), и он сразу же распечатал 0 :

 [[email protected] java]$ gcj --main=Optimize -O2 Optimize.java [[email protected] java]$ ./a.out 0 [[email protected] java]$ 

Может быть, это замечание может быть полезно в этой теме? Почему это так быстро для gcj? Разумеется, одна из причин заключается в том, что gcj компилируется в машинный код и поэтому не имеет возможности оптимизировать этот код на основе поведения кода во время выполнения. Он берет всю свою силу и пытается оптимизировать столько, сколько может при компиляции. Однако виртуальная машина может скомпилировать код Just in Time, поскольку этот вывод java показывает для этого кода:

 class Optimize { private static int doIt() { int x = 0; for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) { x += x + x + x + x + x; } return x; } public static void main(String[] args) { for(int i=0;i<5;i++) { doIt(); } } } 

Выход для java -XX:+PrintCompilation Optimize :

 1 java.lang.String::hashCode (60 bytes) 1% Optimize::doIt @ 4 (30 bytes) 2 Optimize::doIt (30 bytes) 

Как мы видим, JIT компилирует функцию doIt 2 раза. Основываясь на наблюдении за первым исполнением, он компилирует его второй раз. Но он имеет тот же размер, что и байт-код два раза, предполагая, что цикл все еще на месте.

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

В первом примере это оптимизация, которая работает только в том случае, если значение равно нулю. Дополнительная инструкция if в компиляторе, необходимая для поиска этого редко просматриваемого предложения, может просто не стоить того (так как это нужно будет проверять для каждой отдельной переменной). Кроме того, что об этом:

 int x = 1; int y = 1; int z = x - y; for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) { z += z + z + z + z + z; } System.out.println(z); 

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

Некоторые оптимизации позаботятся о втором примере, который вы опубликовали, но я думаю, что я видел его больше на функциональных языках и не столько на Java. Большая вещь, которая усложняет работу на новых языках, - это исправление обезьян . Теперь += может иметь побочный эффект, который означает, что если мы его оптимизируем, это потенциально неверно (например, добавление функциональности к += которая выводит текущее значение, будет означать совсем другую программу).

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

Легче сделать дополнительный момент и убедиться, что то, что вы пишете, - это то, что вы действительно хотите, чтобы компьютер делал. 🙂

Компиляторы в целом очень умны.

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

Такие вещи, как streamовые программы, сглаживание указателей, динамически связанный код и побочные эффекты (системные вызовы / выделение памяти) и т. Д., Делают очень сложным рефакторинг.

Несмотря на то, что ваш пример прост, все еще могут быть трудные ситуации.

Что касается вашего аргумента StringBuilder, это НЕ задание компиляторов, чтобы выбрать, какие структуры данных использовать для вас.

Если вы хотите, чтобы более мощные оптимизации переходили на более строго типизированный язык, такой как fortran или haskell, где компиляторам предоставляется гораздо больше информации для работы.

Большинство курсов, преподающих компиляторы / оптимизацию (даже в acedemically), дают представление о том, как сделать gerneral формально провализованные оптимизаторы, а не взламывать конкретные случаи, является очень сложной проблемой.

Я думаю, вы недооцениваете, насколько много работы, чтобы убедиться, что одна часть кода не влияет на другую часть кода. С небольшим изменением к вашим примерам x, i и s могут указывать на одну и ту же память. Как только одна из переменных является указателем, гораздо сложнее сказать, какой код может иметь побочные эффекты в зависимости от того, указывает ли он на что.

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

Потому что нас просто нет. Вы могли бы так же легко спросить: «Почему мне все еще нужно писать программы … почему я не могу просто загрузить документ требований и написать компьютер для меня?»

Писатели-компиляторы тратят время на мелочи, потому что это те вещи, которые, как правило, пропускают прикладные программисты.

Кроме того, они не могут считать слишком много (может быть, ваша петля была какой-то задержка времени в гетто или что-то в этом роде)?

Это вечная гонка вооружений между авторами компилятора и программистами.

Непродуманные примеры отлично работают – большинство компиляторов действительно оптимизируют явно бесполезный код.

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

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

Поскольку другие правильно рассмотрели первую часть вашего вопроса, я попытаюсь решить вторую часть, то есть «автоматически использует StringBuilder вместо».

Есть несколько веских причин не делать то, что вы предлагаете, но самым большим фактором на практике является вероятность того, что оптимизатор будет работать долго после того, как фактический исходный код будет перевариваться и забыт. Оптимизаторы обычно работают либо с генерируемым байтовым кодом (или сборкой, тремя адресными кодами, машинным кодом и т. Д.), Либо на абстрактных синтаксических деревьях, которые возникают в результате parsingа кода. Оптимизаторы вообще ничего не знают о библиотеках времени исполнения (или каких-либо библиотеках вообще), а вместо этого работают на уровне инструкций (то есть streamе управления низким уровнем и распределении регистров).

Во-вторых, поскольку библиотеки развиваются (особенно на Java) гораздо быстрее, чем языки, не отстают от них и знают, что обесценивает то, что и какой другой компонент библиотеки лучше подходит для этой задачи, будет задачей геркулеса. Вероятно, это невозможно, так как этот предлагаемый оптимизатор должен будет точно понимать как ваше намерение, так и намерение каждого доступного библиотечного компонента и каким-то образом находить сопоставление между ними.

Наконец, как говорили другие (я думаю), автор компилятора / оптимизатора может разумно предположить, что программист, записывающий код ввода, не мертв мозгом. Было бы пустой тратой времени, чтобы посвятить значительные усилия тому, чтобы усугубить особые случаи, подобные этим, когда существуют другие, более общие оптимизации. Кроме того, как отмечали другие, казалось бы, мозговой код может иметь реальную цель (блокировка спина, ожидание до выхода на системный уровень и т. Д.), А компилятор должен соблюдать то, что программист просит (если это синтаксически и семантически корректно).

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

На самом деле Java должен использовать построитель строк во втором примере.

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

Конечно, вы можете придумать тривиальные примеры, но количество тривиальных примеров не ограничено. Вы всегда могли подумать о чем-то другом, так что их не поймать.

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

Но во всяком случае, это тонна работы, и вам это не очень нравится. Люди тратят много времени, пытаясь выяснить способы предотвращения ошибок программ в них, и набирать такие системы, как в Java, и Scala – попытки предотвратить ошибки, но сейчас никто не использует системы типов для создания заявлений о времени выполнения , насколько я знаю.

Возможно, вам захочется заглянуть в Haskel, который, как мне кажется, имеет самую передовую теорию, доказывающую материал, хотя я не уверен в этом. Я сам этого не знаю.

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

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

Это почти считается неправильной практикой для оптимизации таких вещей при компиляции до байт-кода JVM. Sun javac имеет некоторые базовые оптимизации, такие как scalac , groovyc и т. Д. Короче говоря, все, что действительно зависит от языка, может быть оптимизировано внутри компилятора. Однако такие вещи, которые, очевидно, настолько надуманны, чтобы быть агностиком языка, просто проваливаются из политики.

Причиной этого является то, что он позволяет HotSpot иметь гораздо более последовательное представление об байткоде и его шаблонах. Если компиляторы начинают сбрасывать с крайних случаев, это уменьшает способность виртуальной машины оптимизировать общий случай, который может не проявиться во время компиляции. Стив Йегги любит рассказывать об этом: оптимизация часто бывает проще, когда она выполняется во время выполнения умной виртуальной машиной. Он даже доходит до того, что утверждает, что HotSpot выделяет оптимизацию javac. Хотя я не знаю, правда ли это, меня это не удивило бы.

Подводя итог: компиляторы, ориентированные на виртуальные машины, имеют совершенно другой набор критериев, особенно в области оптимизации и когда это уместно. Не следует обвинять авторов компилятора в том, что они оставляют работу на гораздо более эффективной JVM. Как уже неоднократно отмечалось в этой статье, современные компиляторы, ориентированные на собственную архитектуру (например, семейство gcc ), чрезвычайно умны, производя неприлично быстрый код с помощью очень умных оптимизаций.

Во-первых, я никогда не видел смысла в устранении мертвого кода. Почему программист написал это? Если вы собираетесь что-то сделать с мертвым кодом, объявите ошибку компилятора! It almost certainly means the programmer made a mistake–and for the few cases it doesn’t, a compiler directive to use a variable would be the proper answer. If I put dead code in a routine I want it executed–I’m probably planning to inspect the results in the debugger.

The case where the compiler could do some good is pulling out loop invariants. Sometimes clarity says to code the calculation in the loop and having the compiler pull such things out would be good.

Compilers that can do strict-aliasing optimizations, will optimize first example out. См. Здесь .

Second example can’t be optimized because the slowest part here is memory allocation/reallocation and operator+= is redefined into a function that does the memory stuff. Different implementations of strings use different allocation strategies.

I myself also would rather like to have malloc(100000) than thousand malloc(100) too when doing s += “s”; but right now that thing is out of scope of compilers and has to be optimized by people. This is what D language tries to solve by introducing pure functions .

As mentioned here in other answers, perl does second example in less than a second because it allocates more memory than requested just in case more memory will be needed later.

In release mode VS 2010 C++ this doesnt take any time to run. However debug mode is another story.

 #include  int main() { int x = 0; for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) { x += x + x + x + x + x; } printf("%d", x); } 

Absolute optimization is an undecidable problem, that means, there is no Turing machine (and, therefore, no computer program) that can yield the optimal version of ANY given program.

Some simple optimizations can be (and, in fact, are) done, but, in the examples you gave…

  1. To detect that your first program always prints zero, the compiler would have to detect that x remains constant despite all the loop iterations. How can you explain (I know, it’s not the best word, but I can’t come up with another) that to a compiler?

  2. How can the compiler know that the StringBuilder is the right tool for the job without ANY reference to it?

In a real-world application, if efficiency is critical in a part of your application, it must be written in a low-level language like C. (Haha, seriously, I wrote this?)

This is an example of procedural code v. functional code.

You have detailed a procedure for the compiler to follow, so the optimisations are going to be based around the procedure detailed and will minimise any side effects or not optimise where it will not be doing what you expect. This makes it easier to debug.

If you put in a functional description of what you want eg. SQL then you are giving the compiler a wide range of options to optimise.

Perhaps some type of code analysis would be able to find this type of issue or profiling at run-time, but then you will want to change the source to something more sensible.

Because compiler writers try add optimizations for things that matter (I hope) and that are measured in *Stone benchmarks (I fear).

There are zillions of other possible code fragments like yours, which do nothing and could be optimized with increasing effort on the compiler writer, but which are hardly ever encountered.

What I feel embarrassing is that even today most compilers generate code to check for the switchValue being greater than 255 for a dense or almost full switch on an unsigned character. That adds 2 instructions to most bytecode interpreter’s inner loop.

I hate to bring this up on such an old question (how did I get here, anyway?), but I think part of this might be something of a holdout from the days of the Commodore 64.

In the early 1980s, everything ran on a fixed clock. There was no Turbo Boosting and code was always created for a specific system with a specific processor and specific memory, etc. In Commodore BASIC, the standard method for implementing delay s looked a lot like:

 10 FOR X = 1 TO 1000 20 NEXT : REM 1-SECOND DELAY 

(Actually, in practice, it more closely resembled 10FORX=1TO1000:NEXT , but you know what I mean.)

If they were to optimize this, it would break everything—nothing would ever be timed. I don’t know of any examples, but I’m sure there are lots of little things like this scattered through the history of compiled languages that prevented things from being optimized.

Admittedly, these non-optimizations aren’t necessary today. There’s probably, however, some unspoken rule among compiler developers not to optimize things like this. I wouldn’t know.

Just be glad that your code is optimized somewhat, unlike code on the C64. Displaying a bitmap on the C64 could take up to 60 seconds with the most efficient BASIC loops; thus, most games, etc. were written in machine language. Writing games in machine language isn’t fun.

Просто мои мысли.

Premise: I studied compilers at university.

The javac compiler is extremely stupid and performs absolutely no optimization because it relies on the java runtime to do them. The runtime will catch that thing and optimize it, but it will catch it only after the function is executed a few thousand times.

If you use a better compiler (like gcc) enabling optimizations, it will optimize your code, because it’s quite an obvious optimization to do.

Compilers are as smart as we make them. I don’t know too many programmers who would bother writing a compiler that would check for constructs such as the ones you used. Most concentrate on more typical ways to improve performance.

It is possible that someday we will have software, including compilers, that can actually learn and grow. When that day comes most, maybe all, programmers will be out of job.

The meaning of your two examples is pointless, useless and only made to fool the compiler.

The compiler is not capable (and should not be) to see the meaning of a method, a loop or a program. That is where you get into the picture. You create a method for a certain functionality/meaning, no matter how stupid it is. It’s the same case for simple problems or extreme complex programs.

In your case the compiler might optimize it, because it “thinks” it should be optimized in another way but why stay there?

Extreme other situation. We have a smart compiler compiling Windows. Tons of code to compile. But if it’s smart, it boils it down to 3 lines of code…

 "starting windows" "enjoy freecell/solitaire" "shutting down windows" 

The rest of the code is obsolete, because it’s never used, touched, accessed. Do we really want that?

It forces you (the programmer) to think about what you’re writing. Forcing compilers to do your work for you doesn’t help anyone: it makes the compilers much more complex (and slower!), and it makes you stupider and less attentive to your code.

Давайте будем гением компьютера.