2016-05-25 10 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はこの呼び出しをprintfに最適化しないのですか?

は(ジャスト gcc -O3(バージョン5.3.1)で、生成されたアセンブリを確認、または Godbolt Compiler Explorer上)

+1

'gcc'は' printf'出力を予測できません。 – LPs

+1

@LPs AFAIKでは、可能であればputs()とputchar()の呼び出しでprintf()の呼び出しを変更します。 – Mitsos101

+3

@ Mitsos101は*既知のコンパイル時定数のみ*。あなたのコードであなたが見ていることは、それを実行することによってのみ決定できます。簡単にあなたの頭の中で行うことができます。 – adelphus

答えて

11

まず、問題はifではありません。あなたが見たとおり、gccifを見て、30をそのままprintfに渡します。

は今、gccは(特に、それがputs("something")printf("something\n")とさえprintf("%s\n", "something")を最適化しています)printfの特殊なケースを処理するためにいくつかのロジックを持っているが、それは非常に固有のものであり、更に多く行っておりません。例えば、printf("Hello %s\n", "world")はそのまま残されます。さらに悪いことに、の変種のいずれかがの末尾にある場合は、そのままfputs("something", stdout)に変換することができます。

私は、この2つの主要な問題に降りてくることを想像:

  • 上記の2例は、実装するために非常に簡単パターンであり、かなり頻繁に起こるが、残りのため、おそらくそれはめったに努力の価値ません。文字列が一定でパフォーマンスが重要な場合は、プログラマが簡単に処理できます。実際には、printfのパフォーマンスが重要な場合は、このような最適化に頼るべきではありません。文字列。

    上記のputsの最適化だけでもすでに「スタイルポイント」になっている場合でも、人為的なテストケース以外は、実際には深刻なパフォーマンスを得ることはありません。

  • %s\nの領域外に出ると、printfは実行時環境に強く依存するため、地雷です。特に、多くのprintf指定子は(残念なことに)ロケールの影響を受けています。さらに、実装特有の癖や指定子があります(gccはglibc、musl、mingw/msvcrtのprintfで動作します...コンパイル時には、ターゲットのCランタイムを呼び出すことはできません。クロスコンパイル時には思っています)。

    私はこのシンプルな%dのケースはおそらく安全だと同意しますが、なぜ過度にスマートにならないようにして、ここで最も愚かでも安全な最適化のみを行うことにしたのか分かります。好奇心読者の


、この最適化が実際に実施される場合hereです。ご覧のとおり、この機能は限られた数の非常に単純なケースと一致します(GIMPLEを除いて、this nice articleが書かれて以来、多く変更されていません)。ちなみに、ソースは実際に、なぜ改行以外の場合にfputsバリアントを実装できなかったのかを説明しています(そのコンパイル段階でstdoutグローバルを参照する簡単な方法はありません)。

+1

このコメントを書き終える前にあなたの例を修正しましたが(改行で終わっていませんでした)、私はv3を追加したこの[godbolt link anyway](https://godbolt.org/g/mGzqZQ)を投稿しますv4は私がすでに他の何かのために作った例です。 –

6

現代のコンパイラはかなり賢いですが、ロジックを使用して出力を予見してくれません。この場合、人間のプログラマがこのコードを最適化するのは非常に簡単ですが、この作業はマシンにとっては難しいです。実際、プログラムを実行することなくプログラムの出力を予測することは、プログラム(gccなど)では不可能です。証明のために、halting problemを参照してください。

とにかく、入力のないすべてのプログラムがいくつかのputs()ステートメントに最適化されるとは期待していないので、GCCがscanf()ステートメントを含むこのコードを最適化しないのは完全に合理的です。


しかし、これはコンパイラができないか、より最適化された幹部のファイルを生成するように最適化すべきではないという意味ではありません。 すべてのプログラムを予測することは不可能ですが、 には完全に可能であり、期待しています。多くはです。

+1

このサンプルでは、​​コンパイラは実際にiを30に置き換えたので、入力については気にしません。 – pm100

+0

「Halting Problemのためにプログラム分析が完璧になることはできないので、それを気にするべきではありません」というそんなばかげた態度です。プログラムの出力を予測することは一般的に不可能*です。それは、あなたが特定のケースを予測できない(またはすべきではない)ことを意味するわけではありません。既に指摘したように、GCC(まともなコンパイラと同じように)は、 'i'が30になると予測することができ、実際にはそれに基づいてコードを最適化します。 – sepp2k

1

これは説得力のある回答ですが、コンパイラがの場合をputs("10")に最適化してはいけないと思います。

なぜですか?このケースはあなたが思うよりも複雑になる可能性があるからです。ここで私は、現時点ではと考えることができます問題のいくつかは以下のとおりです。

  1. はリテラル、ひいては全体のコードサイズの文字列のASCII 増加サイズに進数に変換します。これは小数には関係ありませんが、printf("some number: %d", 10000) ---- 5桁以上の場合(intが32ビットであると仮定すると)、文字列のサイズが整数に保存されたサイズよりも大きくなり、 。はい、コンバートでは、 "push to stack"命令が保存されましたが、命令のバイト数と保存されるバイト数はアーキテクチャ固有です。それが価値があるかどうかをコンパイラが言うことは自明ではありません。

  2. 埋め込みは、フォーマットで使用する場合、拡張文字列リテラルのサイズを増やすこともできます。例: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についてのみ議論していますが、私は完全性のためにここに言及します。

関連する問題