私はGodを使ってGoのウェブサイトをあちこち調べましたが、Goの異常なビルド時間の説明を見つけることができません。それらは言語機能(またはその欠如)の製品、高度に最適化されたコンパイラ、または何か他のものですか?私は囲碁を宣伝するつもりはありません。気になるだけです。
私はGodを使ってGoのウェブサイトをあちこち調べましたが、Goの異常なビルド時間の説明を見つけることができません。それらは言語機能(またはその欠如)の製品、高度に最適化されたコンパイラ、または何か他のものですか?私は囲碁を宣伝するつもりはありません。気になるだけです。
回答:
依存関係分析。
ゴーよくある質問は、次の文を格納するために使用しました:
Goは、依存関係の分析を容易にし、Cスタイルのインクルードファイルとライブラリのオーバーヘッドの多くを回避するソフトウェア構築のモデルを提供します。
このフレーズはFAQにはもう含まれていませんが、このトピックはGo at Googleのトークで詳しく説明されており、C / C ++とGoの依存関係分析アプローチを比較しています。
これが高速コンパイルの主な理由です。これは仕様によるものです。
Goコンパイラが高速であるというのではなく、他のコンパイラが低速であると思います。
CおよびC ++コンパイラは、大量のヘッダーを解析する必要があります。たとえば、C ++の「hello world」をコンパイルするには、18万行のコードをコンパイルする必要があります。これは、ソースのほぼ半メガバイトです。
$ cpp hello.cpp | wc
18364 40513 433334
JavaとC#コンパイラはVMで実行されます。つまり、何かをコンパイルする前に、オペレーティングシステムはVM全体をロードする必要があり、バイトコードからネイティブコードにJITコンパイルする必要があります。そのすべてに時間がかかります。
コンパイルの速度はいくつかの要因に依存します。
一部の言語は高速にコンパイルされるように設計されています。たとえば、Pascalはシングルパスコンパイラを使用してコンパイルするように設計されています。
コンパイラ自体も最適化できます。たとえば、Turbo Pascalコンパイラは手動で最適化されたアセンブラで記述されており、言語設計と組み合わされて、286クラスのハードウェアで動作する非常に高速なコンパイラをもたらしました。今でも、最新のPascalコンパイラー(FreePascalなど)はGoコンパイラーよりも高速だと思います。
GoコンパイラがほとんどのC / C ++コンパイラよりもはるかに高速である理由はいくつかあります。
主な理由:ほとんどのC / C ++コンパイラは、(コンパイル速度の観点から)非常に悪いデザインを示しています。また、コンパイル速度の観点から、C / C ++エコシステムの一部(プログラマーがコードを記述しているエディターなど)は、コンパイル速度を考慮して設計されていません。
一番の理由:GoコンパイラとGo言語では、高速なコンパイル速度が意識された選択でした
Goコンパイラーには、C / C ++コンパイラーよりも単純なオプティマイザーがあります。
C ++とは異なり、Goにはテンプレートもインライン関数もありません。これは、Goがテンプレートや関数のインスタンス化を実行する必要がないことを意味します。
Goコンパイラは低レベルのアセンブリコードをより早く生成し、オプティマイザはアセンブリコードを処理しますが、一般的なC / C ++コンパイラでは、最適化パスは元のソースコードの内部表現を処理します。C / C ++コンパイラの余分なオーバーヘッドは、内部表現を生成する必要があるという事実から生じます。
Goコンパイラーは使用されているすべてのアセンブリコードを通過し、C / C ++の他の追加アクションも実行しているため、Goプログラムの最終リンク(5l / 6l / 8l)は、C / C ++プログラムのリンクよりも遅くなる可能性があります。リンカーはやっていない
一部のC / C ++コンパイラー(GCC)は(アセンブラーに渡される)テキスト形式で命令を生成しますが、Goコンパイラーはバイナリー形式で命令を生成します。テキストをバイナリに変換するために、追加の作業(多くは必要ありません)を実行する必要があります。
Goコンパイラは少数のCPUアーキテクチャのみをターゲットにしていますが、GCCコンパイラは多数のCPUをターゲットにしています
Jikesなどの高いコンパイル速度を目標に設計されたコンパイラは高速です。2GHzのCPUでは、Jikesは毎秒20000行以上のJavaコードをコンパイルできます(コンパイルのインクリメンタルモードはさらに効率的です)。
コンパイル効率は主要な設計目標でした。
最後に、高速であることを目的としています。単一のコンピューターで大きな実行可能ファイルをビルドするには、最大で数秒かかるはずです。これらの目標を達成するには、多くの言語学的問題に対処する必要があります。同時実行とガベージコレクション。厳密な依存関係の指定; 等々。よくある質問
言語のFAQは、解析に関連する特定の言語機能に関して非常に興味深いものです。
第2に、言語は分析しやすく、シンボルテーブルなしで解析できるように設計されています。
aType
は、変数参照であるかのように式の解析ツリーを作成し、後で意味分析フェーズで、その時点で意味のあるエラーを出力していないことが判明した場合を作成します。
コンパイラーの翻訳効率の良いテストは、自己コンパイルです。特定のコンパイラーがそれ自体をコンパイルするのにどのくらい時間がかかりますか?C ++の場合、非常に長い時間がかかります(時間?)。比較すると、Pascal / Modula-2 / Oberonコンパイラは、最新のマシンで1秒未満でコンパイルされます[1]。
Goはこれらの言語に触発されてきましたが、この効率の主な理由には次のようなものがあります。
効率的にスキャンして解析するために、数学的に正しい明確に定義された構文。
C / C ++のような独立したコンパイルとは対照的に、ヘッダーファイルの不必要な再読み取りと他のモジュールの再コンパイルを回避するために、モジュールの境界を越えて依存関係と型チェックを伴う個別のコンパイルを使用する、タイプセーフで静的にコンパイルされた言語そのようなクロスモジュールチェックはコンパイラーによって実行されません(したがって、単純な1行の「hello world」プログラムの場合でも、すべてのヘッダーファイルを何度も繰り返し読み取る必要があります)。
効率的なコンパイラーの実装(例:シングルパス、再帰下降トップダウン解析)-もちろん、これは上記の1と2の点で非常に役立ちます。
これらの原則は1970年代と1980年代にMesa、Ada、Modula-2 / Oberonなどの言語ですでに知られ、完全に実装されており、Go(Google)のような現代の言語への道を模索しているのは(2010年代)だけです。 、Swift(Apple)、C#(Microsoft)、その他いくつか。
これがすぐに標準になり、例外ではなくなることを期待しましょう。そこにたどり着くには、2つのことが起こる必要があります。
まず、Google、Microsoft、Appleなどのソフトウェアプラットフォームプロバイダーは、既存のコードベースを再利用できるようにする一方で、アプリケーション開発者に新しいコンパイル方法を使用するように働きかける必要があります。これは現在AppleがSwiftプログラミング言語で試みていることであり、Objective-Cと共存できます(同じランタイム環境を使用しているため)。
第2に、基盤となるソフトウェアプラットフォーム自体は、これらの原則を使用して時間の経過とともに最終的に書き換えると同時に、プロセスのモジュール階層を再設計して、モノリシック性を低くする必要があります。これはもちろん巨大なタスクであり、10年のかなりの部分を占める可能性があります(実際にそれを実行するのに十分な勇気がある場合-Googleの場合はまったくわかりません)。
いずれにせよ、言語の採用を推進するのはプラットフォームであり、その逆ではありません。
参照:
[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf、6ページ:「コンパイラは約3秒でコンパイルされます」。この見積もりは、25 MHzのクロック周波数で動作し、1 MByteのメインメモリを備えた低コストのザイリンクスSpartan-3 FPGA開発ボードを対象としています。これから、1 GHzをはるかに超えるクロック周波数と数ギガバイトのメインメモリ(つまり、ザイリンクスSpartan-3 FPGAボードよりも数桁強力)で動作する最新のプロセッサの「1秒未満」に簡単に推定できます。 I / O速度を考慮に入れる場合でも同様です。すでに1990年に、Oberonが2〜4 Mバイトのメインメモリを備えた25MHz NS32X32プロセッサで実行されたとき、コンパイラはわずか数秒でコンパイルされました。実際に待つという概念コンパイラがコンパイルサイクルを終了することは、当時のOberonプログラマにはまったく知られていませんでした。通常のプログラムでは、コンパイラーがトリガーされたコンパイルを完了するのを待つよりも、コンパイルコマンドをトリガーしたマウスボタンから指を離すのに常により多くの時間がかかりました。待ち時間がほぼゼロで、それは本当に即座の満足感でした。そして、生成されたコードの品質は、その時点で利用可能な最高のコンパイラーと必ずしも完全に同じであるとは限りませんが、ほとんどのタスクで著しく良好であり、一般的にはかなり受け入れられました。
Goは高速になるように設計されており、それが示しています。
GOがそのような機能を備えた唯一の言語ではないことに注意してください(モジュールは現代の言語では標準です)。
Alan DonovanとBrian Kernighanによる本「The Go Programming Language」からの引用:
Goコンパイルは、ゼロからビルドする場合でも、他のほとんどのコンパイル済み言語よりも著しく高速です。コンパイラの速度には3つの主な理由があります。まず、すべてのインポートを各ソースファイルの先頭に明示的にリストする必要があるため、コンパイラはファイル全体を読み取って処理し、その依存関係を判断する必要がありません。第2に、パッケージの依存関係は有向非循環グラフを形成し、循環がないため、パッケージを個別に、おそらく並列にコンパイルできます。最後に、コンパイルされたGoパッケージのオブジェクトファイルは、パッケージ自体だけでなく、その依存関係のエクスポート情報も記録します。パッケージをコンパイルするとき、コンパイラーはインポートごとに1つのオブジェクトファイルを読み取る必要がありますが、これらのファイル以外を調べる必要はありません。
コンパイルの基本的な考え方は、実際には非常に単純です。再帰下降パーサーは、原則として、I / Oバウンド速度で実行できます。コード生成は基本的に非常に単純なプロセスです。シンボルテーブルと基本型システムは、多くの計算を必要とするものではありません。
ただし、コンパイラの速度を低下させることは難しくありません。
マルチレベルのインクルードディレクティブ、マクロ定義、条件付きコンパイルを使用するプリプロセッサフェーズがある場合、それらが有用である限り、それをロードするのは難しくありません。(たとえば、WindowsとMFCのヘッダーファイルを考えています。)そのため、プリコンパイル済みヘッダーが必要です。
生成されたコードの最適化に関して、そのフェーズに追加できる処理の量に制限はありません。
単純に(私自身の言葉で)、構文が非常に簡単であるため(分析および解析が容易)
たとえば、型の継承がないということは、新しい型が基本型によって課された規則に従っているかどうかを確認するための問題のある分析ではないということです。
たとえば、次のコード例では、「インターフェース」コンパイラーは移動せず、そのタイプの分析中に、意図したタイプが特定のインターフェースを実装しているかどうかをチェックします。使用されるまで(および使用される場合のみ)、チェックが実行されます。
他の例では、コンパイラーは、変数を宣言してそれを使用していないかどうかを通知します(または戻り値を保持することになっていて、そうではない場合)
以下はコンパイルされません:
package main
func main() {
var a int
a = 0
}
notused.go:3: a declared and not used
この種の強制と原則により、結果のコードがより安全になり、コンパイラーはプログラマーが実行できる追加の検証を実行する必要がなくなります。
これらすべての詳細により、言語の解析が容易になり、コンパイルが高速になります。
もう一度、私自身の言葉で。
ほかに何か?