2

どうやら以前のタイトルを読んだ後、

is ++i fster than i+=1のような質問が存在するのはなぜですか

人々は質問自体を徹底的に読もうとはしませんでした。

質問は、人々がそれを尋ねる理由についてではありませんでした! それは、なぜコンパイラが と の間に違いを生むのか++ii+=1そしてそれが意味をなす可能性のあるシナリオがあるかどうかについてでした。皆さんの機知に富んだ深いコメントには感謝していますが、私の質問はそれに関するものではありませんでした。


さて、質問を別の言い方をしてみましょう。私の英語は十分であり、今回は誤解されずに自分自身を表現できるといいのですが、読んでください。誰かが 10 年前の本でこれを読んだとしましょう:

i=i+1 よりも ++i を使用すると、パフォーマンスが向上します。

私はこの特定の例に熱心ではなく、多かれ少なかれ一般的な話をしています。

明らかに、著者が本を書いていたとき、それは彼にとって意味がありました。彼はただでっち上げたのではありません。++i最新のコンパイラは、i+=1またはを使用するかどうかを気にしないことがわかっていますi = i + 1。コードは最適化され、同じ asm 出力が得られます。

++iこれは非常に論理的に思えます: 2 つの操作が同じことを行い、同じ結果をもたらす場合、 1 つにコンパイルして別のものにコンパイルする理由はありませんi+=1

しかし、本の著者がそれを書いて以来、彼は違いを見ていました! これは、一部のコンパイラが実際にはこれら 2 行に対して異なる出力を生成することを意味します。++iこれは、コンパイラを作成した人たちがと を異なる方法で扱う理由があったことを意味しi+=1ます。私の質問は、なぜ彼らはそうするのでしょうか?

当時、そのような最適化を実行するのに十分なほど高度なコンパイラを作成することが困難/不可能だったからですか? あるいは、いくつかの非常に特定のプラットフォーム/ハードウェア/特別なシナリオでは、 とと その種の他のものとを++i実際に区別することは理にかなっていますか? i+=1それとも、変数の型に依存するのでしょうか? それとも、コンパイラ開発者は単に怠け者だったのでしょうか?

4

4 に答える 4

12

非最適化コンパイラを想像してみてください。++iが同等であるかどうかは気にせずi+=1、最初に思いついたものを出力するだけです。CPU に加算の命令があること、および CPU に整数をインクリメントする命令があることを認識しています。したがって、 itype があると仮定するとint、次の++iようなものが出力されます。

inc <wherever_i_is>

の場合i+=1、次のようなものを出力します。

load the constant 1 into a register
add <wherever_i_is> to that register
store that register to <wherever_i_is>

後者のコードが前者と「同じであるべき」であると判断するために、コンパイラは、追加される定数が 2 または 1007 ではなく 1 であることを認識する必要があります。すべてのコンパイラが常にそれを行っているわけではありません。

したがって、あなたの質問は、「この同等性を見つけたのにそうではないのに、なぜコンパイラーは私よりも愚かなのですか?」ということになります。その答えは、多くの場合、最新のコンパイラはあなたよりも賢いということですが、常にそうとは限らず、常にそうであるとは限りません。

本の著者がそれを書いて以来、彼は違いを見てきました

必ずしも。何が「速い」かについての発言を見た場合、その本の著者は、あなたやコンパイラよりも頭が悪い場合があります。彼は頭が良いこともありますが、もはや当てはまらない状況下での経験則を巧みに作り上げました。彼は時々、私が上で説明したような馬鹿げたコンパイラの存在について推測し、あなたが実際に使用するコンパイラが本当に馬鹿げているかどうかを実際に確認することはありませんでした。私がやったように;-)

ところで、10 年前は、最適化が有効になっているまともなコンパイラにとって、この特定の最適化を行わないにはあまりにも最近のことです。正確なタイムスケールはおそらくあなたの質問には関係ありませんが、著者がそれを書き、彼らの言い訳が「2002年にさかのぼる」である場合、個人的にはそれを受け入れません. その声明は、現在よりも正確ではありませんでした。彼らが 1992 年と言ったとしても、OK です。個人的には、当時のコンパイラがどのようなものだったのかわかりません。矛盾することはできませんでした。彼らが1982年と言ったとしても、私はまだ疑わしいでしょう(結局のところ、C++は当時発明されていました。その設計の多くは、実行時に大量の無駄な作業を避けるために最適化コンパイラに依存していますが、私はそれを認めます.その事実の最大のユーザーは、1982 年には存在しなかったテンプレート コンテナー/アルゴリズムです)。彼らが1972年と言ったら、私は' d おそらくただそれらを信じます。確かに、C コンパイラが美化されたアセンブラだった時代がありました。

于 2012-09-20T11:07:25.647 に答える
9

C では、2 つの式の値が異なるため、i++通常は と同等ではありません。これらは同じ式の値を生成するため、 と同等です。i=i+1++ii=i+1

前述の 3 つの式 with のいずれの値もi使用されていない場合、3 つの値は同じです。優れたコンパイラであれば、 によって生成された未使用の一時変数を最適化できますi++

この一時変数i++は、次の 2 つのことを指示するため、有効になります。

  1. の元の値はi式によって返されますi++
  2. i1インクリメントされます

最初に の元の値を取得して iから をインクリメントiすると、 の元の (古い) 値はi、現在インクリメントされている variable に存在できないため、どこかに存在する必要があります (メモリまたはレジスタは関係ありません) i。それが一時変数です。

OTOH、最初に1ずつインクリメントiした場合、インクリメントを元に戻すために(レジスタまたはメモリ内の)どこかに等しい値を作成する必要があるi-1ため、古い(前にインクリメントされた)値を次の結果として取得できます。表現i++

++iと を使用すると、i=i+1物事ははるかに簡単になります。これらの式は、次の 2 つのことを義務付けています。

  1. i増加する
  2. の新しい値iが返されます

iここでは、最初にインクリメントしてからその値を取得するのが自然です。iand i+1(またはi-1and i) の古い値と新しい値のペアを持つ必要はありません。ここで必要なのは新しいことだけです。

現在、コンパイラの最適化があまり得意ではなかった時代の古い本や古い人がいます。i++そこから、より遅いかもしれないという考えを得ることができます++i。違いは実際に観察されたものであり、補われたものではありません。それは現実であり、今日でもそうであると考える人もいるかもしれません。

また、2 つ (3 つ) のインクリメント式の違いを分析してみると、いくつかの余分な操作を実行し、i++. そしてこの時点で、その人は、この一時的なものがいつ必要でないか、または必要かどうかを検出する方法を理解できない可能性があります. これは、前述の違いに関する質問の別の可能性です。

そしてもちろん、人々は昔からトローリングが大好きでした。:)

コンパイラ開発者が怠け者であるということについては...私はそうではないと思います。理由は次のとおりです。

昔は、コンピューターは現在よりもはるかに遅く、搭載する RAM もはるかに少なくなっていました。

それでもまともな最適化コンパイラを書くことは可能でした。

問題は、最適化のための余分なコードがコンパイラを著しく大きく遅くしていたことです。それが大きければ、それを実行できるコンピューターは少なくなり、それを使用してコードをコンパイルできるプログラマーも少なくなります。他のコンパイラよりも遅い場合、人々は座って待っているのが嫌いなので、他のコンパイラを好むでしょう。

適例:私。私は 90 年代半ばに Borland の Turbo C/C++ にアクセスできました。しかし、私は 90 年代後半から 0 年代前半まで、C を学習して使用することを考えていませんでした。理由?Borland の C/C++ は Pascal よりもはるかに遅く、私の PC はあまり良くありませんでした。コードがコンパイルされるのを待つのは苦痛でした。そして、それがその方法でした。私は最初に Pascal を習得し、後で C と C++ に戻ってきました。

そのため、より賢く、より大きく、より遅いコンパイラーは、コンパイラーのユーザーにお金と時間を費やしていました。最終製品が別のコンパイラでコンパイルされたとしても、少なくともアクティブな開発中は、まだ非常に重要な製品段階です。

また、当時の初歩的なツールを使用して大きなコンパイラ コードを開発および管理することもあまり楽しくなかったことを忘れてはなりません。デバッガー、構文の強調表示、オートコンプリート、ソース コード管理、簡単なファイル比較、インターネット/StackOverflow などを備えた優れた IDE (そして 1 つではありません!) を使用できるようになったのは、今だけです。複数の 20 インチ以上のディスプレイが PC に接続されました! 今、私たちは生産性について話しています! :)

本当に、今日は素晴らしいツールとデバイスを手に入れました。20、30、40 年前、人々はそれらを想像または予測することしかできず、まだ使用していませんでした。

物事はより困難でした。そして、ここで明言するつもりはありませんが、プログラミングが現在ほどコモディティ化されていなかった当時、今日よりも優秀で優秀なプログラマーが多かったことを知っても驚くことではありません。もちろん、これは絶対的な数値ではなく、相対的な数値です。

したがって、コンパイラ担当者が怠け者だったとは思えません。

と呼ばれるものについては、Web を調べてくださいSmall C。これは、C 言語の最も重要な機能のみを実装する、機能を大幅に削減した C コンパイラの一般的な用語です。Ron Cain, (80 年代初期) によるいくつかの実装と、それらからの派生物 (たとえば、およびJames Hendrixによる同じ RatC/Lancaster 実装)を見つけることができます。Bob BerryBrian Meekings

これらのいずれかのコードを見るとSmall C's、最小コード サイズが 2+ KLOC で約 50+ KB であることがわかります。これは、C からアセンブリ コードへの変換にすぎません。ある時点で、誰かがアセンブラでそれを組み立てる必要があります。

最大 48KB の RAM を搭載でき、CPU は ~3MHz で動作する 8 ビットの家庭用コンピューター、たとえば ZX-Spectrum (私が子供の頃に持っていた) のようなもので、このようなプロジェクトで快適に作業することは想像できません。すべてのストレージはテープ レコーダーにあり、データ転送速度は 10KB/分程度で、画面は 80x25 ではなく 32x24 でした。

そして、80 年代初頭の Small C コードはすべて、コンピューターのメモリにかろうじて収まり、何も最適化しませんでした!

于 2012-09-20T11:17:38.907 に答える
2

あなたがどの本を参照しているのか完全にはわからないので、元の引用を調べることはできません. ただし、著者は組み込み型について完全に話していたわけではないと思います。組み込み型の場合、式++ii += 1、およびi = i + 1は同等であり、コンパイラーは最も効率的なものを選択する可能性が最も高いですが、ランダムアクセス反復子などの他のタイプの場合、それらは必ずしも同等ではありません。意味的には、それらは同等であることを意図していますが、コンパイラにはこの意味的な知識がなく、実装はとにかく異なることを行う可能性があります。組み込み型を使用する場合でも、クラス型のオブジェクトを使用する場合に最も効率的である可能性が高いフォームの記述に慣れることで、不要なパフォーマンスの問題を回避できます。「自動的に」最も効果的な方法を使用しているため、料金を支払う必要はありません。注意しすぎ。

関連する演算子を提供するクラスを定義する場合 (たとえば、ランダム アクセス反復子を作成する場合)、コンパイラはコードが同等であると判断できない場合があります。この理由の 1 つは、関数がインライン化されていない場合など、コードが必ずしも表示されないことです。関数がインライン化されている場合でも、コンパイラが追跡できない副作用が発生する可能性があります。ランダム アクセス反復子の実装では、内部的にポインターを使用++pし、と を使用することが非常に適切p += nです。nただし、たまたま値の定数である情報が失われた瞬間に1、それを置き換えることはできませんp += n++pもう。コンパイラは定数の折りたたみに優れていますが、少なくともコード全体がインラインであり、コンパイラがインライン関数を実際にインライン化する必要があると判断している必要があります。

于 2012-09-23T13:35:40.883 に答える
1

答えは、タイプによって異なりますi

クラスが実装されると、プリインクリメント ( T & T::operator++()、ポストインクリメント ( T T::operator ++(int))、加算 ( T T::operator +(T const &)(とりわけ))) およびインクリメント ( T T::operator +=(T const &)) の異なる演算子があります。(明らかに、これらすべてのバリアントがあります)。

十分に自明なタイプの場合、これらはおそらくすべて大量です。

ただし、重要なタイプの場合、パフォーマンスはそれらの記述方法に依存します。一般に:

  • a++++aインクリメントする前にオブジェクトのコピーを返す必要があるため、よりも高速になる可能性は低いです。
  • a = a + ba += b最初はテンポラリを作成する必要があるため、これよりも高速になる可能性は低いです。
  • a += 1++aは a と同じ型ではない可能性があるため、それよりも高速になる可能性は低く、それ1を解決するために必要な処理が必要になる可能性があります。
  • クラスによっては、これらの操作の一部が利用できない場合があります。

それを超えて、確実に言える唯一のことは、コードを確認してパフォーマンス テストを実行する必要があるということです。

于 2012-09-20T10:55:44.803 に答える