2016-05-25 4 views
14
#include <stdio.h> 
int main(void) { 
    int i; 
    scanf("%d", &i); 
    if(i != 30) { return(0); } 
    printf("i is equal to %d\n", i); 
} 

Оказывается, что результирующая строка всегда будет «я равен 30», так что, почему не GCC оптимизировать этот вызов PRINTF с вызовом puts() или write(), например?Почему GCC не оптимизирует этот вызов для печати?

(Только что проверил сгенерированную сборку, с gcc -O3 (версия 5.3.1), или на Godbolt Compiler Explorer)

+1

'gcc' не делает (не может ..?) Предсказать вывод' printf'. – LPs

+1

@LPs AFAIK, он вызывает вызовы printf() с вызовами puts() и putchar(), где это возможно. – Mitsos101

+3

@ Mitsos101 только с * известными константами времени компиляции *. То, что вы смотрите в своем коде, может быть определено только путем его запуска. Легко для вас сделать в своей голове - менее легко для компилятора. – adelphus

ответ

11

Прежде всего, проблема заключается не в if; как вы видели, gcc видит через if и проходит мимо 30 прямо на printf.

Теперь gcc имеет некоторую логику для обработки особых случаев printf (в частности, делает оптимизацию printf("something\n") и даже printf("%s\n", "something") к puts("something")), но она весьма специфична и не идти дальше; printf("Hello %s\n", "world"), например, остается как есть. Хуже того, любой из вариантов выше без конечной новой строки остаются нетронутыми, даже если они могут быть преобразованы в fputs("something", stdout).

Я полагаю, что это сводится к двум основным проблемам:

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

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

  • Когда вы начинаете выходить за пределы области %s\n, printf - это минное поле, потому что оно имеет сильную зависимость от среды выполнения; в частности, многие спецификаторы printf (к сожалению) затронуты языковой версией, плюс есть лебедка специфичных для реализации особенностей и спецификаторов (и gcc может работать с printf от glibc, musl, mingw/msvcrt, ...- и во время компиляции вы не можете вызывать целевое время выполнения C - подумайте, когда вы выполняете кросс-компиляцию).

    Я согласен с тем, что этот простой случай %d, вероятно, безопасен, но я могу понять, почему они, вероятно, решили избежать чрезмерной умности и выполнять самые тупые и безопасные оптимизации здесь.


Для любознательных читателей, here, где эта оптимизация фактически реализуется; как вы можете видеть, функция соответствует ограниченному числу очень простых случаев (и GIMPLE в сторону, не сильно изменилась с this nice article, изложив их, было написано). Кстати, источник на самом деле объясняет, почему они не могут реализовать вариант fputs для случая, не связанного с новой чертой (нет простого способа ссылаться на глобальный stdout на этом этапе компиляции).

+1

Прежде чем я закончил этот комментарий, вы исправили свой пример (который не заканчивался новой строкой), но я все равно отправлю это [ссылка на godbolt] (https://godbolt.org/g/mGzqZQ), где я добавил v3 и v4 к примеру, который я уже сделал для чего-то другого. –

6

Современные компиляторы достаточно умны, но не достаточно умен, чтобы предвидеть вывода с помощью логики. В этом случае для программистов-программистов довольно просто оптимизировать этот код, но эта задача слишком сложна для машин. Фактически, предсказание вывода программы без запуска невозможно для программ (например, gcc). Для доказательства см. halting problem.

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


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

+1

в этом примере компилятор фактически заменил i на 30, поэтому он не заботится о вводе. Его просто недостаточно умно, чтобы знать, что он может заменить printf wiht puts – pm100

+0

«Анализ программ никогда не может быть идеальным из-за проблемы с остановкой, поэтому мы не должны его беспокоить» - такое смехотворное отношение. Прогнозировать вывод программ невозможно * вообще *. Это не значит, что вы не можете (или не должны) прогнозировать конкретные случаи. Как уже отмечалось, GCC (как и любой достойный компилятор) вполне способен предсказать, что 'i' будет 30, и фактически оптимизирует код на основе этого. – sepp2k

1

Не уверен, что это убедительный ответ, но я ожидаю, что компиляторы не должны оптимизировать printf("%d\n", 10) к puts("10").

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

  1. Преобразование двоичных чисел в ASCII увеличивает размер строкового литерала, и, таким образом, общий размер кода. Хотя это не имеет значения для небольших чисел, но если это printf("some number: %d", 10000) ---- 5 или более цифр (при условии, что int - 32-разрядный), размер строки увеличится, будет бить размер, сохраненный для целого числа, и некоторые люди могут считать это недостатком , Да, с преобразованием я сохранил инструкцию «push to stack», но сколько байтов является инструкцией и сколько будет сохранено, зависит от архитектуры. Для компилятора нетривиально сказать, стоит ли это.

  2. Padding, если используется в форматах, также может увеличить размер расширенного строкового литерала. Пример: printf("some number: %10d", 100)

  3. Иногда разработчик будет делить строку формата среди PRINTF вызовов по причинам размера кода:

    printf("%-8s: %4d\n", "foo", 100); 
    printf("%-8s: %4d\n", "bar", 500); 
    printf("%-8s: %4d\n", "baz", 1000); 
    printf("%-8s: %4d\n", "something", 10000); 
    

    Преобразование их в разные строки литералов может потерять преимущество размера.

  4. Для %f, %e и %g есть проблема с десятичной точкой «.». зависит от локали. Следовательно, компилятор не может расширять его до строковой константы для вас. Хотя мы обсуждаем только %d, я упоминаю об этом здесь для полноты.