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

Я всегда удивляюсь, почему компиляторы не могут понять простые вещи, которые очевидны для человеческого глаза. Они делают много простых оптимизаций, но никогда не являются чем-то даже немного сложным. Например, этот код занимает около 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. Конечно, это глупые примеры, которые никогда нельзя использовать нигде. Но всякий раз, когда мне приходится переписывать красивый и очень читаемый код во что-то нечитаемое, чтобы компилятор был счастлив и создавал быстрый код, мне интересно, почему компиляторы или какой-либо другой автоматизированный инструмент не могут сделать это для меня.

Ответ 1

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

#include <stdio.h>  /* 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 предварительно вычисляет factorial(10) во время компиляции. Он даже не вызывает factorial(). Невероятный. Я снимаю шляпу перед командой разработчиков GCC.

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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


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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Компилятор С++ позволяет оптимизировать этот фрагмент кода и просто добавить правильное значение в 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 (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 года.

Ответ 8

В первом примере это оптимизация, которая работает, только если значение равно нулю. Дополнительная инструкция 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. Большая вещь, которая делает это трудным в более новых языках, является исправлением обезьяны. Теперь += может иметь побочный эффект, который означает, что если мы его оптимизируем, это потенциально неправильно (например, добавление функции к += которая выводит текущее значение, будет означать совсем другую программу).

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

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

Ответ 9

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

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

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

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

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

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

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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

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

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

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

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

Ответ 13

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

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

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

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

Ответ 14

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

Ответ 15

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

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

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

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

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

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

Ответ 16

Абсолютная оптимизация - неразрешимая проблема, это означает, что нет машины Тьюринга (и, следовательно, нет компьютерной программы), которая может обеспечить оптимальную версию ЛЮБОЙ данной программы.

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

  • Чтобы обнаружить, что ваша первая программа всегда печатает нуль, компилятор должен будет обнаружить, что x остается постоянным, несмотря на все итерации цикла. Как вы можете объяснить (я знаю, это не лучшее слово, но я не могу придумать другое), что для компилятора?

  • Как компилятор знает, что StringBuilder является правильным инструментом для задания без ЛИЧНОЙ ссылки на него?

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

Ответ 17

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

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

Ответ 18

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

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

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

Ответ 19

Я никогда не видел смысла в устранении мертвого кода в первую очередь. Почему программист написал это? Если вы собираетесь что-то сделать с мертвым кодом, объявите ошибку компилятора! Это почти наверняка означает, что программист ошибся, и для немногих случаев это не так, директива компилятора использовать переменную будет правильным ответом. Если я положу мертвый код в рутину, я хочу, чтобы он был выполнен - ​​я, вероятно, планирую проверить результаты в отладчике.

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

Ответ 20

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

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

Я сам тоже хотел бы иметь malloc (100000), чем тысяча malloc (100) тоже при выполнении s + = "s"; но прямо сейчас, что вещь выходит за рамки компиляторов и должна быть оптимизирована людьми. Это то, что D-язык пытается решить, введя чистые функции.

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

Ответ 21

В режиме выпуска VS 2010 С++ это не займет времени. Однако режим отладки - это еще одна история.

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

Ответ 22

Это пример функционального кода процедурного кода v.

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

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

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

Ответ 23

Поскольку авторы компилятора пытаются добавить оптимизацию для вещей, которые имеют значение (я надеюсь), и которые измеряются в тестах "Стоун" (я боюсь).

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

То, что мне кажется неловким, заключается в том, что даже сегодня большинство компиляторов генерируют код для проверки того, что switchValue больше 255 для плотного или почти полного переключения на неподписанном символе. Это добавляет 2 инструкции для внутреннего цикла интерпретатора байт-кода.

Ответ 24

Мне не нравится приводить это к такому старому вопросу (как я сюда попал?), но я думаю, что часть этого может быть чем-то вроде удержания со времен Commodore 64.

В начале 1980-х все работало на фиксированных часах. Не было Turbo Boosting, и код всегда создавался для конкретной системы с конкретным процессором и конкретной памятью и т.д. В Commodore BASIC стандартный метод реализации delay выглядел так:

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

(Фактически, на практике он более напоминал 10FORX=1TO1000:NEXT, но вы знаете, что я имею в виду.)

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

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

Просто радуйся, что ваш код несколько оптимизирован, в отличие от кода на C64. Отображение растрового изображения на C64 может занимать до 60 секунд с наиболее эффективными циклами BASIC; таким образом, большинство игр и т.д. были написаны машинным языком. Написание игр на машином языке не забавно.

Только мои мысли.

Ответ 25

Помещение: Я изучал компиляторы в университете.

Компилятор javac крайне глуп и не выполняет абсолютно никакой оптимизации, потому что он полагается на время выполнения java для их выполнения. Среда выполнения поймает эту вещь и оптимизирует ее, но она поймает ее только после того, как функция будет выполнена несколько тысяч раз.

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

Ответ 26

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

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

В вашем случае компилятор может его оптимизировать, поскольку он "думает", что он должен быть оптимизирован по-другому, но зачем оставаться там?

Крайняя иная ситуация. У нас есть компилятор с компилятором, который компилирует Windows. Тонны кода для компиляции. Но если он умный, он сводит его до трех строк кода...

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

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

Ответ 27

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

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

Ответ 28

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