コードは
int a = ((1 + 2) + 3); // Easy to read
より遅く走る
int a = 1 + 2 + 3; // (Barely) Not quite so easy to read
または、「無駄な」括弧を削除/最適化するのに十分な最新のコンパイラーです。
非常に小さな最適化の懸念のように思えるかもしれませんが、C#/ Java / ...よりもC ++を選択することは、最適化(IMHO)のすべてです。
コードは
int a = ((1 + 2) + 3); // Easy to read
より遅く走る
int a = 1 + 2 + 3; // (Barely) Not quite so easy to read
または、「無駄な」括弧を削除/最適化するのに十分な最新のコンパイラーです。
非常に小さな最適化の懸念のように思えるかもしれませんが、C#/ Java / ...よりもC ++を選択することは、最適化(IMHO)のすべてです。
回答:
コンパイラは実際に括弧を挿入または削除することはありません。それはあなたの表現に対応する解析木(括弧が存在しない)を作成するだけで、そうする際にはあなたが書いた括弧を尊重しなければなりません。式を完全に括弧で囲むと、その解析ツリーが何であるかがすぐに人間の読者に明らかになります。のように露骨に冗長な括弧を入れて極端に行くと、int a = (((0)));
結果の解析ツリー(したがって生成されたコードを変更せずに、パーサーでいくつかのサイクルを浪費しながら、リーダーのニューロンにいくつかの無駄なストレスを引き起こします)ほんの少し。
括弧を記述しない場合でも、パーサーは構文解析ツリーを作成するという仕事をしなければならず、演算子の優先順位と結合性の規則は、どの構文解析ツリーを構築する必要があるかを正確に伝えます。これらのルールは、コードに挿入する(暗黙の)括弧をコンパイラに指示するものと考えるかもしれませんが、この場合、パーサーは実際に括弧を処理することはありません。特定の場所に存在していました。int a = (1+2)+3;
(の関連性+
は左にあります)のように、これらの場所に括弧を配置すると、パーサーはわずかに異なるルートで同じ結果に到達します。のように異なる括弧を入れた場合int a = 1+(2+3);
その後、異なる解析ツリーを強制し、異なるコードが生成される可能性があります(ただし、結果ツリーを構築した後にコンパイラが変換を適用する可能性があるため、結果のコードを実行する効果が変わらない限り、それ)。再送信するコードに違いがあると仮定すると、一般的にはどちらがより効率的であるかを言うことはできません。もちろん、最も重要な点は、ほとんどの場合、解析ツリーは数学的に同等の式を提供しないため、実行速度を比較することはポイントの横にあります。適切な結果が得られる式を記述する必要があります。
つまり、正確さのために必要に応じて、そして読みやすさのために必要に応じて括弧を使用します。冗長な場合、それらは実行速度にまったく影響を与えません(そしてコンパイル時間にほとんど影響を与えません)。
また、これはいずれも最適化とは関係ありません。最適化は、解析ツリーが構築された後に行われるため、解析ツリーがどのように構築されたかを知ることはできません。これは、最も古くて愚かなコンパイラーから、最もスマートで最新のコンパイラーに変更することなく適用されます。インタプリタ言語(「コンパイル時間」と「実行時間」が一致する場合)でのみ、余分な括弧のペナルティが生じる可能性がありますが、そのような言語のほとんどは、少なくとも解析フェーズが1回だけ実行されるように構成されていると思います各ステートメント(実行のために事前に解析された形式を保存)。
a = b + c * d;
、a = b + (c * d);
[無害]冗長な括弧になります。それらがコードをより読みやすくするのに役立つなら、うまくいきます。a = (b + c) * d;
非冗長な括弧になります-実際に結果の解析ツリーを変更し、異なる結果を与えます。行うことは完全に合法です(実際、必要です)が、暗黙のデフォルトのグループ化とは異なります。
括弧は、コンパイラーではなく、ユーザーの利益のためだけにあります。コンパイラーは、ステートメントを表す正しいマシンコードを作成します。
参考までに、コンパイラは可能な限り完全に最適化するのに十分賢いです。あなたの例では、これはint a = 6;
コンパイル時に変わります。
int a = 8;// = 2*3 + 5
int five = 7; //HR made us change this to six...
あなたが実際に尋ねた質問に対する答えはノーですが、あなたが尋ねたつもりだった質問に対する答えはイエスです。括弧を追加しても、コードが遅くなることはありません。
最適化について質問しましたが、括弧は最適化とは関係ありません。コンパイラは、生成されたコードのサイズまたは速度のいずれか(場合によっては両方)を改善する目的で、さまざまな最適化手法を適用します。たとえば、式A ^ 2(Aの2乗)を取り、それが速い場合はA x A(Aをそれ自体で乗算)に置き換えます。ここでの答えは「いいえ」です。コンパイラーは、括弧が存在するかどうかによって最適化フェーズで何も変わりません。
読みやすさを向上させると思われる場所で、不必要な括弧を式に追加した場合、コンパイラが同じコードを生成するかどうかを尋ねたつもりだったと思います。言い換えると、括弧を追加すると、コンパイラーは、何らかの形で貧弱なコードを生成するのではなく、括弧を再び取り出すのに十分なほど賢くなります。答えは常に「はい」です。
慎重に言いましょう。厳密に不要な(式の評価の意味や順序に影響を与えない)式に括弧を追加すると、コンパイラーはそれらを静かに破棄し、同じコードを生成します。
ただし、明らかに不要な括弧が実際に式の評価の順序を変更する特定の式が存在し、その場合、コンパイラは実際に書いたものを有効にするコードを生成します。これは意図したものとは異なる場合があります。以下に例を示します。これをしないでください!
short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c; // ok
int d = (-a + b) + c; // ok, same code
int d = (-a + b + c); // ok, same code
int d = ((((-a + b)) + c)); // ok, same code
int d = -a + (b + c); // undefined behaviour, different code
必要に応じて括弧を追加しますが、それらが本当に不要であることを確認してください!
私は決してしません。本当の利益をもたらさないエラーのリスクがあります。
脚注:符号付き整数式が、表現できる範囲外の値(この場合は-32767〜+32767)に評価されると、符号なしの動作が発生します。これは複雑なトピックであり、この回答の範囲外です。
a
本当に符号なしの-a + b
場合a
、負の値とb
正の値の場合、計算を先導することも簡単にオーバーフローする可能性があります。
(b+c)
最後の行の式はその引数をint
にプロモートします。そのため、コンパイラint
が16ビットであると定義しない限り(古代または小さなマイクロコントローラーをターゲットにしているため)、最後の行は完全に正当です。
int
をint
除き、昇格されるよりも小さいものはすべて昇格されunsigned int
ます。定義されたすべての動作がプロモーションが含まれている場合と同じ場合、コンパイラはプロモーションを省略できます。16ビット型がラッピング抽象代数リングとして動作するマシンでは、(a + b)+ cとa +(b + c)は同等です。場合はint
、オーバーフロー上に捕捉されたことを16ビットのタイプだった、しかし、その後、例が存在することになるところの表現の1 ...
ブラケットは、演算子の優先順位を操作するためのものです。コンパイルすると、実行時にブラケットが不要になるため、ブラケットは存在しなくなります。コンパイルプロセスは、あなたと私が必要とするすべての括弧、スペース、その他の構文糖を削除し、すべての演算子を、コンピューターが実行するためのはるかに単純なものに変更します。
だから、あなたと私が見るかもしれない場所...
...コンパイラは次のようなものを出力する場合があります。
プログラムは、先頭から開始して各命令を順番に実行することにより実行されます。
演算子の優先順位は「先着順」になりました。
コンパイラーは元の構文をバラバラにしていた間、すべてがうまく機能したため、すべてが強く型付けされています。
わかりました、それはあなたと私が扱うもののようなものではありませんが、それから私たちはそれを実行していません!
浮動小数点かどうかによって異なります:
浮動小数点演算では、加算は結合的ではないため、オプティマイザーは操作を並べ替えることができません(fastmathコンパイラースイッチを追加しない限り)。
整数演算では、並べ替えることができます。
この例では、両方がまったく同じコードにコンパイルされるため、両方がまったく同じ時間に実行されます(加算は左から右に評価されます)。
ただし、javaとC#でさえ最適化でき、実行時に実行されます。
いいえ、しかしはい、しかし多分、しかし多分他の方法、しかしいいえ。
既に指摘されているように(C、C ++、C#、Javaなど、左に連想される言語を想定)、式((1 + 2) + 3)
はとまったく同じ1 + 2 + 3
です。これらは、ソースコードに何かを記述するさまざまな方法であり、結果のマシンコードまたはバイトコードに影響を与えません。
いずれにせよ、結果は、たとえば2つのレジスタを追加してから3番目を追加する命令、またはスタックから2つの値を取得、追加、プッシュバック、それから別の値を取得して追加、または3つのレジスタを追加する命令になります単一の操作、または次のレベルで最も賢明なもの(マシンコードまたはバイトコード)に応じて3つの数値を合計する他の方法。バイトコードの場合、これと同様の再構築が行われる可能性があります。たとえば、これに相当するIL(スタックへの一連のロード、および結果を追加してプッシュバックするペアのポップ)マシンコードレベルでそのロジックを直接コピーすることにはなりませんが、問題のマシンにとってより賢明なものになります。
しかし、あなたの質問にはさらに何かがあります。
正常なC、C ++、Java、またはC#コンパイラの場合、指定した両方のステートメントの結果が次とまったく同じ結果になると予想されます。
int a = 6;
結果のコードがリテラルの計算に時間を浪費するのはなぜですか?プログラムの状態を変更して1 + 2 + 3
も6
、being の結果が停止することはありません。そのため、実行中のコードではこれが必要です。実際、それでもないかもしれません(その6で何をするかに応じて、すべてを捨てることができるかもしれません;そして、C#でさえ「大幅に最適化しないでください、ジッタはこれを最適化するからです」同等int a = 6
または単に不要なものをすべて捨てる)。
しかし、これはあなたの質問の可能な拡張に私たちを導きます。以下を考慮してください。
int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;
そして
int c;
if(d < 100)
c = 0;
else
c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);
(この最後の2つの例は有効なC#ではありませんが、他のすべてはC#、C ++、およびJavaで有効です。)
ここでも、出力に関してまったく同じコードがあります。定数式ではないため、コンパイル時に計算されません。1つのフォームが別のフォームよりも高速である可能性があります。どちらが速いですか?それは、プロセッサと、おそらくかなりarbitrary意的な状態の違いに依存します(特に高速な場合は、それほど高速ではない可能性が高いため)。
そして、それらはあなたの質問と完全に無関係ではありません。それらはほとんどが概念的に何かが行われる順序の違いに関するものだからです。
それぞれに、一方が他方よりも高速であると疑う理由があります。単一のデクリメントには特殊な命令がある場合があるため、(b / 2)--
実際にはの場合よりも高速になり(b - 2) / 2
ます。d * 32
おそらくそれよりも速くd << 5
製造することで、d * 32 - d
より速く製造できましたd * 31
。最後の2つの違いは特に興味深いものです。1つはいくつかのケースでいくつかの処理をスキップすることを許可しますが、もう1つは分岐の予測ミスの可能性を回避します。
したがって、これにより2つの質問が残ります。2.コンパイラは、遅いものを速いものに変換しますか?
そして答えは1です。2.たぶん。
または、問題のプロセッサに依存するため、拡張するには依存します。確かに、一方のナイーブなマシンコードの同等物が他方のナイーブなマシンコードの同等物よりも速いプロセッサが存在していました。電子コンピューティングの歴史の中で、常に高速なものもありませんでした(特に、分岐予測ミス要素は、パイプライン化されていないCPUがより一般的であった多くの場合、関連していませんでした)。
そしておそらく、コンパイラー(およびジッター、スクリプトエンジン)が実行するさまざまな最適化があり、特定のケースでは必須になるものもありますが、通常、論理的に同等なコードの一部を見つけることができるでしょう最もナイーブなコンパイラーでさえ、まったく同じ結果と論理的に同等のコードの一部を持ち、最も洗練されたものでも、一方より他方のコードの方が速いコードを生成します(ポイントを証明するために完全に病理学的なものを記述する必要がある場合でも)。
非常に小さな最適化の懸念のように思えるかもしれませんが、
いいえ。ここで示したものよりも複雑な違いがあっても、最適化とはまったく関係のない絶対に小さな懸念のようです。どちらかといえば、読みにくいのは読み((1 + 2) + 3
やすいよりも遅いかもしれないと疑うので、それは悲観的な問題です1 + 2 + 3
。
しかし、C#/ Java / ...よりもC ++を選択することは、最適化(IMHO)のすべてです。
それが本当にC#やJavaよりもC ++を選択することが「すべて」である場合、人々はStroustrupとISO / IEC 14882のコピーを焼き付け、C ++コンパイラのスペースを解放して、さらにMP3または何かの余地を残すべきだと思います。
これらの言語には、それぞれ異なる利点があります。
その1つは、C ++の方が一般的にメモリの使用が高速で軽量であることです。ええ、C#やJavaがより高速であり、アプリケーションのライフタイムでメモリをより適切に使用できる例があります。これらは関連する技術が向上するにつれて一般的になりつつありますが、C ++で記述された平均的なプログラムはこれらの2つの言語のいずれかで同等のものよりも高速で、より少ないメモリを使用する、より小さな実行可能ファイル。
これは最適化ではありません。
最適化は、「物事を速くする」ことを意味するために使用されることがあります。多くの場合、「最適化」について実際に話しているときは、実際に物事を速くすることについて話しているので、一方が他方の速記になっているので、私は自分でその言葉を誤用することを認めます。
「物事を速くする」という正しい言葉は最適化ではありません。ここでの正しい言葉は改善です。プログラムに変更を加えた場合、唯一の重要な違いは、プログラムがより高速になり、最適化されずに改善されることです。
最適化とは、特定の側面や特定のケースに関して改善を行うことです。一般的な例は次のとおりです。
このような場合は、たとえば次の場合に正当化されます。
しかし、そのような場合は他のシナリオでも正当化されません。品質の絶対的な絶対的尺度によってコードが改善されたわけではなく、特定の用途により適した特定の点で改善されました。最適化。
言語の選択はここで効果があります。速度、メモリの使用、読みやすさはすべてそれに影響される可能性がありますが、他のシステムとの互換性、ライブラリの可用性、ランタイムの可用性、特定のオペレーティングシステムでのランタイムの成熟度も影響を受ける可能性があります(私の罪のために、私はどういうわけかLinuxとAndroidを私のお気に入りのOSとして、C#を私のお気に入りの言語として使用することになり、Monoは素晴らしいですが、私はまだこの1つにかなり思いつきます)。
「C#/ Java / ...よりもC ++を選択することは最適化に関することです」と言うのは、C ++が本当に悪いと思う場合にのみ意味があります。C ++の方が優れていると思う場合、最後に必要なことは、そのような可能な限り小さなマイクロオプトについて心配することです。確かに、おそらくそれを放棄する方が良いでしょう。幸せなハッカーも最適化する品質です!
ただし、「C ++が大好きで、C ++で気に入っていることの1つが余分なサイクルを絞り出すことだ」と言いたければ、それは別の問題です。マイクロオプトは、再帰的な習慣になる可能性がある場合にのみ価値があります(つまり、自然にコーディングする傾向は、低速であるよりも頻繁に高速になります)。さもなければ、彼らは時期尚早な最適化でさえなく、事態を悪化させる早すぎる悲観的です。
括弧は、式を評価する順序をコンパイラに指示するためにあります。とにかく使用される順序を指定するため、それらは役に立たないことがあります(読みやすさを向上または悪化させることを除く)。順番が変わることもあります。に
int a = 1 + 2 + 3;
事実上、存在するすべての言語には、1 + 2を加算し、結果に3を加算して合計を評価するというルールがあります。
int a = 1 + (2 + 3);
括弧は別の順序を強制します。最初に2 + 3を追加し、次に1に結果を追加します。括弧の例は、とにかく生成されるのと同じ順序を生成します。この例では、演算の順序はわずかに異なりますが、整数加算の動作方法は同じです。に
int a = 10 - (5 - 4);
括弧は重要です。それらを省略すると、結果は9から1に変わります。
コンパイラーがどの操作をどの順序で実行するかを決定した後、括弧は完全に忘れられます。この時点でコンパイラが覚えているのは、どの操作をどの順序で実行するかだけです。そのため、実際にはコンパイラが最適化できるものは何もありません。括弧はなくなりました。
practically every language in existence
; APLを除く:(ここ)[tryapl.org]に(1-2)+3
(2)、1-(2+3)
(-4)、および1-2+3
(-4)を入力してみてください。
言われたことの多くに同意しますが、ここで重要なことは、操作の順序を強制するために括弧が存在するということです...これはコンパイラーが絶対にやっていることです。はい、それはマシンコードを生成します…しかし、それはポイントではなく、求められているものでもありません。
括弧は実際になくなりました。既に述べたように、それらはマシンコードの一部ではなく、数字であり、他のものではありません。アセンブリコードはマシンコードではなく、人間が読める形式であり、オペコードではなく名前で指示が含まれています。マシンは、オペコードと呼ばれるもの、つまりアセンブリ言語の数値表現を実行します。
Javaなどの言語は、それらを生成するマシン上で部分的にしかコンパイルされないため、中間の領域に分類されます。それらは、それらを実行するマシン上でマシン固有のコードにコンパイルされますが、この質問には違いはありません-括弧は最初のコンパイル後に消えます。
a = f() + (g() + h());
、コンパイラは自由に呼び出すことができf
、g
およびh
その順序で(あるいはそれが喜ば任意の順序で)。
コンパイラーは、言語に関係なく、すべてのインフィックス数学をポストフィックスに変換します。言い換えると、コンパイラが次のようなものを見たとき:
((a+b)+c)
これは次のように変換されます。
a b + c +
これは、中置記法は読みやすいが、後置記法はコンピュータがジョブを実行するために必要な実際の手順に非常に近いためです(そして、すでに十分に開発されたアルゴリズムがあるため)。定義では、postfixは操作順序や括弧に関するすべての問題を排除します。これにより、実際にマシンコードを記述するときに物事が自然に簡単になります。
このテーマの詳細については、逆ポーランド記法に関するウィキペディアの記事をお勧めします。