web-dev-qa-db-ja.com

Goはどのようにすばやくコンパイルできますか?

Googleで検索してGoのWebサイトを調べましたが、Goの異常なビルド時間の説明が見つからないようです。それらは言語機能の製品(またはその欠如)、高度に最適化されたコンパイラ、または他の何かですか? Goを宣伝するつもりはありません。興味があります。

203
Evan Kroske

依存関係分析。

Go FAQ から:

Goは、依存関係の分析を容易にし、Cスタイルのインクルードファイルとライブラリのオーバーヘッドの多くを回避するソフトウェア構築のモデルを提供します。

それが高速コンパイルの主な理由です。これは設計によるものです。

180
Igor Krivokon

Goコンパイラがfastであるのではなく、他のコンパイラがslowであると思います。

CおよびC++コンパイラは、膨大な量のヘッダーを解析する必要があります。たとえば、C++の「hello world」をコンパイルするには、18k行のコードをコンパイルする必要があります。

$ cpp hello.cpp | wc
  18364   40513  433334

JavaおよびC#コンパイラはVMで実行されます。つまり、何かをコンパイルする前に、オペレーティングシステムはVM全体をロードする必要があり、バイトコードからネイティブコードにJITでコンパイルする必要があります。

コンパイルの速度はいくつかの要因に依存します。

一部の言語は、高速でコンパイルされるように設計されています。たとえば、Pascalはシングルパスコンパイラを使用してコンパイルされるように設計されました。

コンパイラ自体も最適化できます。たとえば、Turbo Pascalコンパイラーは、手動で最適化されたアセンブラーで記述され、言語設計と組み合わせて、286クラスのハードウェアで動作する非常に高速なコンパイラーになりました。今でも、最新のPascalコンパイラ(FreePascalなど)はGoコンパイラよりも高速だと思います。

70
el.pescado

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は1秒あたり20,000行以上のJavaコードをコンパイルできます(そして、コンパイルのインクリメンタルモードはさらに効率的です)。

34
user811773

コンパイル効率は、主要な設計目標でした。

最後に、高速であることを目的としています。1台のコンピューターで大規模な実行可能ファイルをビルドするのに最大で数秒かかります。これらの目標を達成するためには、多くの言語上の問題に対処する必要がありました。並行性とガベージコレクション。厳密な依存関係の仕様。等々。 FAQ

言語FAQは、解析に関連する特定の言語機能に関してかなり興味深いものです。

第二に、この言語は分析しやすく、シンボルテーブルなしで解析できるように設計されています。

32
Larry OBrien

上記のほとんどが当てはまりますが、実際には言及されていない非常に重要なポイントが1つあります。それは、依存関係の管理です。

Goは、インポートするパッケージのみを含める必要があります直接(すでにインポートしたものとしてthey必要)。これは、C/C++とはまったく対照的です。C/ C++では、すべての単一ファイルは、xヘッダーを含み、yヘッダーなどを含みます。/C++は指数関数的な時間がかかります。

23
Kosta

コンパイラの翻訳効率の良いテストは自己コンパイルです:与えられたコンパイラがそれ自身をコンパイルするのにどれくらい時間がかかりますか? C++の場合、非常に長い時間がかかります(数時間?)。比較すると、Pascal/Modula-2/Oberonコンパイラは、最新のマシンではone second未満でコンパイルされます[1]。

Goはこれらの言語に触発されていますが、この効率性の主な理由には次のものがあります。

  1. 効率的にスキャンおよび解析するための、数学的に適切な明確に定義された構文。

  2. separateコンパイルwith依存関係と型チェックacrossモジュール境界を使用する、タイプセーフで静的にコンパイルされた言語。ヘッダーファイルおよび他のモジュールの再コンパイル-independent C/C++のようなコンパイルではなく、そのようなクロスモジュールチェックはコンパイラーによって実行されません(したがって、すべてのヘッダーを再読み込みする必要があります)単純な1行の「hello world」プログラムであっても、ファイルを何度も繰り返します)。

  3. 効率的なコンパイラー実装(シングルパス、再帰下降トップダウン解析など)-もちろん、上記のポイント1と2によって大いに役立ちます。

これらの原則はすでに知られ、1970年代および1980年代にMesa、Ada、Modula-2/Oberonなどの言語で完全に実装されており、現在(2010年代)だけでGo(Google)などの現代言語に移行しています。 、Swift(Apple)、C#(Microsoft)およびその他いくつか。

これがすぐに標準になり、例外ではなくなることを期待しましょう。そこに到達するには、次の2つのことが必要です。

  1. まず、Google、Microsoft、Appleなどのソフトウェアプラットフォームプロバイダーは、既存のコードベースを再利用できるようにすると同時に、application開発者に新しいコンパイル方法の使用を奨励することから始める必要があります。これはAppleがSwiftプログラミング言語で今やろうとしていることで、Objective-Cと共存できます(同じランタイム環境を使用しているため)。

  2. 第二に、これらの原則を使用して、基盤となるソフトウェアプラットフォーム自体を最終的に書き直し、同時にプロセスのモジュール階層を再設計して、モノリシックを少なくする必要があります。これはもちろん巨大なタスクであり、10年のうちの大部分を占めるかもしれません(実際にそれを実行するのに十分勇気がある場合-Googleの場合はまったくわかりません)。

いずれにせよ、言語の採用を推進するのはプラットフォームであり、その逆ではありません。

参照:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf 、6ページ:「コンパイラーは約3秒でコンパイルされます」。この見積もりは、25 MHzのクロック周波数で動作し、1 Mバイトのメインメモリを備えた低コストのザイリンクスSpartan-3 FPGA開発ボードを対象としています。これからeasily 1 GHzをはるかに超えるクロック周波数と数Gバイトのメインメモリで実行される最新のプロセッサの「1秒未満」に外挿できます(つまり、ザイリンクスよりも数桁強力です) Spartan-3 FPGAボード)、I/O速度を考慮しても。 1990年、Oberonが2〜4 Mバイトのメインメモリを搭載した25MHz NS32X32プロセッサで実行されていた頃、コンパイラはわずか数秒でコンパイルされました。実際にwaitingという概念は、コンパイラがコンパイルサイクルを終了することを当時のOberonプログラマーに完全に知られていませんでした。典型的なプログラムの場合、alwaysは、コンパイラーがトリガーされたばかりのコンパイルを完了するのを待つよりも、コンパイルコマンドをトリガーしたマウスボタンから指を離すのに時間がかかりました。ほぼゼロの待機時間で、本当にすぐに満足できました。そして、生成されたコードの品質は、その時点で利用可能な最高のコンパイラーと常に完全に一致していなかったとしても、ほとんどのタスクに非常に優れており、一般に非常に受け入れられました。

21
Andreas

Goは高速になるように設計されており、それが示しています。

  1. 依存関係管理:ヘッダーファイルはありません。直接インポートされるパッケージを見るだけでよいため(インポートするものを心配する必要はありません)、線形の依存関係があります。
  2. 文法:言語の文法は単純であるため、簡単に解析できます。機能の数は減りますが、コンパイラコード自体はタイトです(パスはほとんどありません)。
  3. オーバーロードは許可されていません。シンボルが表示され、どのメソッドが参照されているかがわかります。
  4. 各パッケージは独立してコンパイルできるため、Goを並行してコンパイルすることは簡単です。

そのような機能を備えた言語はGOだけではないことに注意してください(モジュールは現代の言語では標準です)が、うまく機能しました。

11
Matthieu M.

コンパイルの基本的な考え方は、実際には非常に単純です。再帰下降パーサーは、原則として、I/Oの制限された速度で実行できます。コード生成は基本的に非常に簡単なプロセスです。シンボルテーブルと基本型システムは、多くの計算を必要とするものではありません。

ただし、コンパイラを遅くすることは難しくありません。

複数レベルのincludeディレクティブ、マクロ定義、および条件付きコンパイルを備えたプリプロセッサフ​​ェーズがあり、それらが有用であれば、それをロードするのは難しくありません。 (1つの例として、WindowsとMFCヘッダーファイルを考えています。)だから、プリコンパイル済みヘッダーが必要です。

生成されたコードの最適化に関しては、そのフェーズに追加できる処理の量に制限はありません。

8
Mike Dunlavey

アラン・ドノヴァンとブライアン・カーニガンによる本「 The Go Programming Language 」から引用:

Goコンパイルは、ゼロから構築する場合でも、他のほとんどのコンパイル言語よりも著しく高速です。コンパイラの速度には3つの主な理由があります。まず、すべてのインポートは各ソースファイルの先頭に明示的にリストされる必要があるため、コンパイラはファイル全体を読み取って処理し、依存関係を判断する必要がありません。第二に、パッケージの依存関係は有向非循環グラフを形成し、サイクルがないため、パッケージを個別に、おそらく並行してコンパイルできます。最後に、コンパイルされたGoパッケージのオブジェクトファイルには、パッケージ自体のエクスポート情報だけでなく、その依存関係のエクスポート情報も記録されます。パッケージをコンパイルするとき、コンパイラはインポートごとに1つのオブジェクトファイルを読み取る必要がありますが、これらのファイルを超えて見る必要はありません。

7
Miscreant

単純に(自分の言葉で)、構文が非常に簡単であるため(分析および解析)

たとえば、型の継承とは、新しい型が基本型によって課された規則に従っているかどうかを調べるための問題のない分析ではありません。

たとえば、このコード例の場合: "interfaces" コンパイラーは、意図したタイプimplementそのタイプを分析しながら、指定されたインターフェイスを。使用されるまで(および使用される場合のみ)、チェックが実行されます。

他の例では、変数を宣言していて使用していない場合(または戻り値を保持することになっていて、そうでない場合)、コンパイラーから通知されます

以下はコンパイルされません:

package main
func main() {
    var a int 
    a = 0
}
notused.go:3: a declared and not used

この種の強制と principles により、結果のコードはより安全になり、コンパイラはプログラマが実行できる追加の検証を実行する必要がなくなります。

概して、これらすべての詳細により言語の解析が容易になり、コンパイルが高速になります。

繰り返しますが、私自身の言葉で。

4
OscarRyz

goはコンパイラの作成と並行して設計されたため、生まれたときから親友でした。 (IMO)

2
Andrey