ethan

ethan

新知,热爱生活,码农,读书
twitter
email
github

Rob PikeがGoogle Goについて語る

1. Rob、あなたは Google Go という言語を作成しました。Google Go とは何ですか?Google Go について簡潔に紹介していただけますか?#

この言語を作成した理由について話しましょう。あなたの質問とは少し異なりますが、私は Google でプログラミング言語に関する一連の講義を行い、YouTube にもあります。そこで、私が初期に書いた言語、Newsqueak について触れました。それは 1980 年代のことで、非常に早い時期のことです。講義を行っている間に、なぜ Newsqueak のいくつかのアイデアが、現在私が C++ を主に使用している作業環境では使えないのかを考え始めました。また、Google では非常に大きなプログラムを構築することが多く、構築するだけでも多くの時間がかかりますし、依存関係の管理にも問題があります。本来必要のないものをリンクしてしまうため、バイナリパッケージが非常に大きくなり、リンク時間も長く、コンパイル時間も長くなります。C++ の作業方式は少し古く、実際には C に基づいており、C++ はすでに 30 年の歴史があり、C はさらに 40 年の歴史があります。現代のハードウェアで計算を行う際には、多くの新しい要素を考慮する必要があります:マルチコアマシン、ネットワーク化、分散システム、クラウドコンピューティングなど。

2. Go の主な特徴は何ですか?重要な機能はありますか?#

ほとんどの人にとって、Go の第一印象は、この言語が並行性を言語の原語として扱っていることです。これは、私たちが分散計算やマルチコアのようなものを扱う際に非常に良く、重要です。多くの人が Go を単純で退屈な言語だと思うかもしれませんが、特に目立ったものはないように見えるからです。しかし、実際には第一印象で Go を判断することはできません。Go を使用したことのある多くの人は、それが非常に生産的で表現力豊かな言語であり、私たちがこの言語を作成したときに解決できることを期待していたすべての問題を解決できることに気づくでしょう。

Go のコンパイルプロセスは非常に速く、バイナリパッケージは比較的小さく、依存関係の管理は言語自体の管理と同じように行われます。ここにはもう一つの話がありますが、ここでは詳しくは述べません。この言語の並行性は、非常に複雑な操作や分散計算環境を非常にシンプルなパターンで処理できるようにします。最も重要な機能はおそらく並行性でしょう。その後、この言語の型システムについて話すことができますが、C++ や Java のような従来のオブジェクト指向型システムとは大きく異なります。

3. 話を続ける前に、Go コンパイラがなぜそんなに速いコンパイル速度を達成できるのか説明していただけますか?何か秘訣がありますか?#

それが速い理由は二つあります。まず、Go には二つのコンパイラがあります —— 二つの独立した実装です。一つは Plan 9(http://plan9.bell-labs.com/wiki/plan9/1/)スタイルで新たに書かれたコンパイラで、独自の作業方式を持つ全く新しいコンパイラです。もう一つのコンパイラは GCC Go と呼ばれ、GCC のフロントエンドを持っています。このコンパイラは Ian Taylor によって後に書かれました。したがって、Go には二つのコンパイラがあり、速度の速さは両者の共通の特徴ですが、Plan 9 スタイルのコンパイラの速度は GCC Go の 5 倍です。なぜなら、それは最初から最後まで全く新しいもので、GCC のバックエンドがないため、良いコードを生成するのに多くの時間がかかることはありません。

GCC Go コンパイラはより良いコードを生成するため、速度は遅くなります。しかし、実際に重要なのは、Go コンパイラの依存関係管理機能がそのコンパイル速度の速さの本当の理由です。C または C++ プログラムを見てみると、ヘッダーファイルがライブラリ、オブジェクトコードなどを記述していることがわかります。言語自体は依存関係を強制的にチェックしないため、毎回コードを分析して関数がどのようなものであるかを明確にする必要があります。コンパイルプロセス中に別のクラスの C++ プログラムを使用したい場合、その依存関係のあるクラスやヘッダーファイルなどを先にコンパイルする必要があります。コンパイルする C++ プログラムに多くのクラスがあり、内部で関連している場合、同じヘッダーファイルを数百回、さらには千回もコンパイルすることになるかもしれません。もちろん、プリコンパイルヘッダーファイルや他のテクニックを使用してこの問題を回避することもできます。

しかし、言語自体はあなたを助けることはできません。ツールはこの問題を改善するかもしれませんが、最大の問題は、コンパイルするものがプログラムに本当に必要なものであることを保証するものがないことです。あなたのプログラムには本当に必要のないヘッダーファイルが含まれている可能性がありますが、言語は強制的にチェックしないため、あなたはそれを知ることができません。一方、Go にはより厳格な依存関係モデルがあり、「パッケージ」と呼ばれるものがあります。これを Java のクラスファイルやそれに類似したもの、またはライブラリのようなものと考えることができますが、実際には異なりますが、基本的な考え方は同じです。重要な点は、あるものが別のものに依存していて、その別のものがさらに別のものに依存している場合、例えば A が B に依存し、B が C に依存している場合、最初に最も内側の依存関係をコンパイルする必要があるということです。つまり、最初に C をコンパイルし、その後 B をコンパイルし、最後に A をコンパイルします。

しかし、A が B に依存しているが、A は C に直接依存していない場合、依存関係が伝播しているとしたら、どうすればよいのでしょうか?この場合、B が C から必要な情報をすべて B のオブジェクトコードに格納します。こうして、A をコンパイルする際に C を気にする必要がなくなります。したがって、非常にシンプルなことになります:プログラムをコンパイルする際には、依存関係ツリーを上にたどって型情報を取得するだけで、ツリーの頂点に到達すれば、隣接する依存関係だけをコンパイルすればよく、他のレベルの依存関係を気にする必要はありません。算術演算を行う場合、Objective-C や C++ などの言語では、単純なヘッダーファイルを含んでいるだけでも、依存関係の伝播があるため、数十万行のプログラムをコンパイルすることになるかもしれません。しかし、Go では、ファイルを開くと、そこには 20 行しかないかもしれません。なぜなら、公共インターフェースだけが記述されているからです。

依存関係のチェーンに 3 つのファイルしかない場合、Go の利点はそれほど明確ではありませんが、数千から数万のファイルがある場合、Go の速度の利点は指数的に増加します。私たちは、Go を使用すれば、数百万行のコードを数秒でコンパイルできるはずだと信じています。しかし、同じ量の C++ で書かれたプログラムでは、依存関係管理の問題により、コンパイルのオーバーヘッドがはるかに大きく、コンパイル時間は数分に達するでしょう。したがって、Go の速度の根源は主に依存関係の管理に起因しています。

4. Go の型システムについて話しましょう。Go には構造体(struct)や型(type)がありますが、Go の型とは何ですか?#

Go の型は、他の伝統的なプログラミング言語の型と似ています。Go の型には整数、文字列、構造体データ構造、配列(array)があり、これらはスライス(slice)と呼ばれ、C の配列に似ていますが、より使いやすく、より固定的です。ローカル型を宣言して名前を付け、通常の方法で使用することができます。Go とオブジェクト指向の違いは、型はデータを記述する方法の一つであり、メソッドは完全に独立した概念であるということです。メソッドを構造体に置くことができ、Go にはクラスの概念がなく、代わりに構造体とその構造体に宣言されたメソッドがあります。

構造体はクラスと混同してはいけません。しかし、メソッドを配列、整数、浮動小数点数、文字列に置くこともでき、実際にはどんな型にもメソッドを持たせることができます。したがって、ここでのメソッドの概念は Java のメソッドよりも一般的です。Java では、メソッドはクラスの一部に過ぎません。例えば、整数にメソッドを持たせることができますが、それはあまり役に立たないように思えるかもしれません。しかし、もし「Tuesday」という整数定数に to_string メソッドを追加して美しい曜日形式で印刷したい場合や、文字列を再フォーマットして異なる方法で自分自身を印刷できるようにしたい場合、その重要性に気づくでしょう。なぜすべてのメソッドや他の良いものをクラスに詰め込む必要があるのでしょうか?なぜそれらをより広範なサービスとして提供しないのでしょうか?

5. これらのメソッドはパッケージ内でのみ見えるのですか?#

いいえ、実際にはそうではありません。Go では、実装した型に対してパッケージ内でのみメソッドを定義することが許可されています。他の型をインポートして直接メソッドを追加することはできませんが、匿名フィールド(anonymous field)を使用してそれをラップすることができます。メソッドは好きなところに追加できるわけではなく、型を定義してからその上にメソッドを置く必要があります。したがって、私たちはパッケージ内で別のカプセル化の方法 —— インターフェース(interface)を提供していますが、誰がオブジェクトにメソッドを追加できるかの厳密な境界を理解しないと、インターフェースを理解するのは難しいです。

6. あなたの言うことは、int にメソッドを追加できますが、typedef を使用する必要があるということですか?#

整数型を typedef し、名前を付ける必要があります。例えば、一週間の 7 日を扱っている場合、「Day」と名付けることができます。あなたが宣言した型 ——Day にメソッドを追加することはできますが、int に直接メソッドを追加することはできません。なぜなら、整数型はあなたが定義したものではなく、あなたのパッケージに存在しないからです。それはインポートされていますが、あなたのパッケージ内で定義されていないため、メソッドを追加することはできません。あなたのパッケージ内で定義されていない型にメソッドを追加することはできません。

7. あなたたちは Ruby のオープンクラスの考え方を取り入れたのですね。それは面白いです。Ruby のオープンクラスは実際にはクラスを変更し、新しいメソッドを追加することができるため、破壊的ですが、あなたたちの方法は本質的に安全です。なぜなら、新しいものを作成しているからです。#

それは安全で制御可能で、理解しやすいです。最初は、型を使うのが不便かもしれないと思っていましたが、Ruby のようにメソッドを追加したいとも思いましたが、そうするとインターフェースが理解しにくくなります。そこで、私たちはメソッドを取り出すことにしました。私たちはそれを入れるよりも良い方法を思いつかなかったので、メソッドをローカル型にのみ制限しましたが、この考え方は非常に理解しやすく、使いやすいです。

8. あなたは typedef について言及しましたが、それは typedef と呼ばれますか?#

「type」と呼ばれるべきです。あなたが言った型 ——Day の定義方法は「type Day int」のようになります。これにより、新しい型ができ、その上にメソッドを追加したり、変数を宣言したりできますが、この型は int とは異なり、C のように単なる別名ではなく、Go では実際に int とは異なる新しい型「Day」を作成します。これは int の構造的特性を持っていますが、独自のメソッドセットを持っています。

9. C の typedef はプリプロセッサ命令ですか?【編集注 / 免責事項:C 言語の typedef はプリプロセッサとは無関係です】#

それは実際にはエイリアスですが、Go ではエイリアスではなく、新しい型です。

10. 基本的なことから始めましょう。Go で最小の型は何ですか?#

最小の型はブール型(bool)でしょう。bool、int、float、次に int32、float64 のようなサイズのある型、文字列、複素型があり、漏れがあるかもしれませんが、これが基本的な型の集合です。これらの型を使用して構造体、配列、マップ(map)を構築できます。マップは Go の組み込み型であり、関数ライブラリではありません。そして、インターフェースが続きます。インターフェースに到達すると、面白いことが本当に始まります。

11. しかし、int のような型は値型ですよね。#

int は値型です。Go では、すべての型は値型であり、C と同様に、すべてのものは値によって呼び出されますが、ポインタを使用することもできます。何かを参照したい場合、そのアドレスを取得することができ、ポインタを持つことになります。Go にもポインタがありますが、C のポインタよりも多くの制限があります。Go のポインタは安全であり、型安全であるため、コンパイラを欺くことはできず、ポインタ演算もありません。したがって、何かを指すポインタがある場合、それをオブジェクトの外に移動させたり、コンパイラを欺いたりすることはできません。

12. それは C++ の参照に似ていますか?#

はい、参照に非常に似ていますが、期待する方法で書き込み操作を行うことができます。また、構造体内部(バッファなど)の特定のアドレスを使用することもできます。これは Java の参照とは異なります。Java では、隣にバッファを割り当てる必要があり、これは追加のオーバーヘッドです。Go では、実際にそのオブジェクトを構造体の一部として割り当て、同じメモリブロック内に配置します。これはパフォーマンスにとって非常に重要です。

13. それは構造体内部の複合オブジェクトです。#

はい、もしそれが値であれば、ポインタではなく、そうです。もちろん、ポインタを構造体の内部や外部に置くこともできますが、もしあなたが struct A を持ち、struct B を struct A の中に置くと、struct B は一つのメモリブロックになります。これは Java のようではなく、これも Java のパフォーマンス問題の一因です。

14. あなたはインターフェースが非常に面白いと述べましたので、次にこの部分について話しましょう。#

Go のインターフェースは本当に非常に、非常にシンプルです。インターフェースは二つの異なることを示しています。一つは、型の考え方を示し、インターフェース型は一連のメソッドを列挙した型です。したがって、行動を定義するために一連のメソッドを抽象化したい場合、インターフェースを定義し、これらのメソッドを宣言します。これで、インターフェース型と呼ばれる型ができ、今後はインターフェース内のこれらのメソッドを実装したすべての型 —— 基本型、構造体、マップ(map)または他の型も、暗黙的にそのインターフェースの要件を満たします。もう一つの本当に面白い点は、他の多くの言語のインターフェースとは異なり、Go には「implements」宣言がないことです。

あなたは「私のオブジェクトはこのインターフェースを実装しています」と明示する必要はありません。インターフェース内のメソッドを定義するだけで、自動的にそのインターフェースを実装します。これに対して心配する人もいますが、私の見解では、彼らが言いたいのは、自分が何を実装しているかを知ることが本当に重要だということです。もし自分が何を実装しているかを確認したい場合、実際にはそれを行うためのテクニックがあります。しかし、私たちの考え方は全く異なります。私たちの考えは、何のインターフェースを実装するかを考えるべきではなく、やるべきことを書くべきだということです。事前にどのインターフェースを実装するかを決定する必要はありません。後で、実際に今は知らないインターフェースを実装することになるかもしれません。そのインターフェースはまだ設計されていないかもしれませんが、今あなたはそれを実装しています。

後で、元々考慮していなかった二つのクラスが関連性を持つことに気づくかもしれません —— またクラスという言葉を使ってしまいましたが、Java を考えすぎています —— 二つの構造体が非常に便利な小さなサブセットの関連メソッドを実装している場合、これらの構造体のいずれかを操作できる方法が非常に便利になります。こうして、インターフェースを宣言し、何も気にする必要がなくなります。たとえこれらのメソッドが他の人のコードで実装されていても問題ありません。あなたはそのコードを編集することはできませんが、Java の場合、これらのコードはあなたのインターフェースを実装することを宣言する必要があります。ある意味で、実装は一方向です。しかし、Go では、実装は双方向です。インターフェースには実際に多くの美しくシンプルな例があります。

私が最も好きな実際の例は「Reader」です。Go には IO というパッケージがあり、IO パッケージには Reader インターフェースがあります。これは一つのメソッドを持ち、そのメソッドは標準的な read メソッドの宣言です。これは、オペレーティングシステムやファイルから内容を読み取るためのものです。このインターフェースは、システム内の read システムコールを行うものによって実装されることができます。明らかに、ファイル、ネットワーク、キャッシュ、解凍機、暗号化機、パイプ、さらにはデータにアクセスしたいものは何でも、Reader インターフェースを提供できます。これにより、これらのリソースからデータを読み取るプログラムは、このインターフェースを通じて目的を達成できます。これは前述の Plan 9 に似ていますが、異なる方法で一般化されています。

同様に、Writer も理解しやすい別の例です。Writer は書き込みを行う人によって実装されます。したがって、フォーマットされた印刷を行う際、fprintf の最初のパラメータはファイルではなく Writer になります。これにより、fprintf は write メソッドを実装したものに対して IO フォーマットの作業を行うことができます。非常に良い例がたくさんあります。例えば HTTP の場合、HTTP サーバーを実装している場合、単に connection に対して fprintf を行うことで、データをクライアントに送信できます。特別な操作は必要ありません。圧縮機を介して書き込みを行うことができ、私が言及したものを介して書き込みを行うことができます:圧縮機、暗号化機、キャッシュ、ネットワーク接続、パイプ、ファイルなど、すべて write メソッドを実装しているため、暗黙的に writer インターフェースの要件を満たしています。

15. ある意味で構造化型システム(structural typing)に似ていますね。#

その振る舞いを考慮しない場合、それは構造化型システムに似ています。しかし、それは完全に抽象的であり、何を持っているかではなく、何ができるかに重点を置いています。構造体(struct)を持つことで、そのメモリの形状が規定され、メソッドが構造の振る舞いを説明し、その後、インターフェースがその構造と同じメソッドを実装した他の構造のメソッドを抽象化します。これはダックタイピング(duck typing、動的型システムの一種)であり、構造化型システムではありません。

16. あなたはクラスについて言及しましたが、Go にはクラスはありませんよね。#

Go にはクラスはありません。

17. しかし、クラスがないのにどうやってコードを書くのですか?#

メソッドを持つ構造体(struct)はクラスに非常に似ています。興味深い違いは、Go にはサブタイプの継承がないことです。Go の代替的な書き方を学ぶ必要があります。Go にはより強力で表現力豊かなものがあります。しかし、Java プログラマーや C++ プログラマーが最初に Go を使用する際、彼らは驚くかもしれません。なぜなら、実際には Go を使用して Java プログラムや C++ プログラムを書いているからです。そのようなコードはうまく機能しません。あなたはそれを行うことができますが、少し不器用になります。しかし、一歩引いて、「Go を使ってこれらのものを書くにはどうすればよいか?」と自問すると、パターンが実際には異なることに気づくでしょう。Go を使用すると、同様のアイデアをより短いプログラムで表現できるため、すべてのサブクラスで動作を繰り返し実装する必要がありません。これは非常に異なる環境であり、最初に見たときよりもはるかに異なります。

18. もし私がいくつかの動作を実装したい場合、そしてそれを複数の構造体に共有したい場合、どうすればよいですか?#

匿名フィールドという概念、つまり埋め込み(embedding)があります。その動作はこうです:もしあなたが構造体(struct)を持ち、他の何かがあなたが望む動作を実装している場合、それらをあなたの構造体に埋め込むことができます。こうすることで、その構造体は埋め込まれたもののデータだけでなく、そのメソッドも取得できます。もしあなたがいくつかの共通の動作を持っている場合、例えば特定の型に name メソッドがある場合、Java ではこれは一組のサブクラス(継承されたメソッド)だと考えるでしょう。しかし、Go では、name メソッドを持つ型を取得し、そのメソッドを実装したいすべての構造体に置くだけで、これらは自動的に name メソッドを取得します。すべての構造体にこのメソッドを書く必要はありません。これは非常にシンプルな例ですが、埋め込みを使用している多くの興味深い構造化されたものがあります。

さらに、あなたは一つの構造体に複数のものを埋め込むこともできます。これは多重継承のように考えることができますが、実際には Go では非常にシンプルです。これは単なる集合であり、あなたはその中に何でも置くことができ、基本的にすべてのメソッドを結合し、各メソッドの集合に対して、一行のコードを書くことでそのすべての動作を持つことができます。

19. もし多重継承の名前の衝突の問題があったら、どうすればよいですか?#

名前の衝突は実際にはそれほど問題ではありません。Go はこの問題を静的に処理します。そのルールは、複数のレベルの埋め込みがある場合、最上位が優先されるということです。同じレベルに同じ名前や同じメソッドが二つある場合、Go は単純な静的エラーを出します。あなたは自分でチェックする必要はなく、このエラーに注意を払うだけで済みます。名前の衝突は静的チェックであり、ルールは非常にシンプルです。実際には、名前の衝突が発生することはあまりありません。

20. システムにルートオブジェクトやルートクラスがない場合、異なる型の構造体のリストを取得したい場合、どうすればよいですか?#

インターフェースの面白い点は、それらが単なる集合であり、メソッドの集合であることです。したがって、空の集合、つまりメソッドがないインターフェースを持つことができます。システム内のすべてのものが空のインターフェースの要件を満たします。空のインターフェースは Java の Object に似ていますが、違いは、int、float、string も空のインターフェースを満たすことです。Go は実際のクラスを必要としません。なぜなら、Go にはクラスの概念がなく、すべてのものが統一されているからです。これは voidに似ていますが、voidはポインタに対してであり、値に対してではありません。

しかし、空のインターフェースの値はシステム内の任意のものを表すことができ、非常に普遍的です。したがって、空のインターフェースの配列を作成すると、実際には多態性のコンテナを持つことになります。もしそれを取り出したい場合、Go には型スイッチがあり、アンパックする際にその中の型を尋ねることができるため、安全にアンパック操作を行うことができます。

21. Go には Goroutines というものがあり、これらは coroutines とは何が違いますか?違いはありますか?#

Coroutines と Goroutines は異なります。その名前がそれを反映しています。私たちは新しい名前を付けました。なぜなら、用語が多すぎるからです。プロセス(processes)、スレッド(threads)、軽量スレッド、コルチン(coroutines)など、数え切れない名前があります。そして、Goroutines も新しいものではありません。同じ概念は他のシステムにも存在します。しかし、この概念は前述の名前とは大きく異なります。私たちは自分たちの名前を付けたかったのです。Goroutine の背後にある意味は、これは coroutine ですが、ブロックされた後に他の coroutine に移行するということです。同じスレッド上の他の coroutines も移行するため、ブロックされることはありません。

したがって、根本的に言えば、Goroutines は coroutines の一種であり、十分な数の操作スレッドで多重特性を得ることができ、Goroutines が他の coroutine によってブロックされることはありません。もしそれらが単に協力しているだけなら、一つのスレッドで十分です。しかし、IO 操作が多い場合、多くのオペレーティングシステムのアクションが発生し、多くのスレッドが必要になります。しかし、Goroutines は非常に安価であり、数十万の Goroutines を持つことができ、全体的に良好に動作し、合理的な量のメモリしか占有しません。Goroutines は非常に安価に作成でき、ガベージコレクション機能もあり、すべてが非常にシンプルです。

22. あなたは mスレッドモデルを使用していると述べました。つまり、m 個の coroutines が n 個のスレッドにマッピングされるということですか?#

はい、しかし coroutines の数とスレッドの数は、プログラムが行う作業によって動的に決まります。

23. Goroutines には通信のためのチャネルがありますか?#

はい、二つの独立した機能がある場合、Goroutine が相互に協力する必要があるため、相互に対話する必要があります。そこでチャネルという概念が生まれました。これは実際には型メッセージキューであり、値を送信するために使用できます。Goroutine の一端を保持している場合、型の値をもう一方に送信することができ、その一端は必要なものを受け取ります。チャネルには同期と非同期の二種類があり、可能な限り同期チャネルを使用します。なぜなら、同期チャネルの考え方は非常に良く、同期と通信を同時に行うことができ、すべてのものが調和して動作します。

しかし、時には効率やスケジューリングの理由から、メッセージをキャッシュすることも意味があります。あなたはチャネルに整数メッセージ、文字列、構造体、構造体へのポインタなど、何でも送信することができます。非常に面白いことに、あなたはチャネル上に別のチャネルを送信することができます。こうして、他の人との通信をあなたに送信することができるという非常に面白い概念が生まれます。

24. あなたはキャッシュされた同期チャネルと非同期チャネルがあると述べました。#

いいえ、同期はキャッシュされていません。非同期とキャッシュは同じ意味です。なぜなら、キャッシュがあることで、値をキャッシュスペースに保存できるからです。しかし、キャッシュがない場合、他の人が値を取り去るのを待たなければなりません。したがって、無キャッシュと同期は同じ意味です。

25. 各 Goroutine は小さなスレッドのようです。読者にそう説明できますか?#

はい、しかし軽量です。

26. それらは軽量です。しかし、各スレッドも事前にスタックスペースを割り当てるため、非常にリソースを消費します。Goroutines はどのように処理していますか?#

その通りです。Goroutines が作成されるとき、非常に小さなスタック ——4K しかありません。これは少し小さいかもしれません。このスタックはヒープ内にあります。もちろん、C 言語でこんなに小さなスタックがあったらどうなるかはご存知でしょう。関数を呼び出したり、配列を割り当てたりする際、プログラムはすぐにオーバーフローします。しかし、Go ではそのようなことは起こりません。各関数の冒頭には、スタックポインタがその制限に達しているかどうかをチェックするいくつかの命令があります。制限に達した場合、他のブロックにリンクされます。このようなリンクされたスタックはセグメントスタックと呼ばれます。もし最初に起動したときよりも多くのスタックを使用した場合、このスタックブロックのリンクが形成されます。

このメカニズムは非常に安価です。もちろん、複数のスタックブロックを割り当てることもできますが、Go コンパイラは大きなものをヒープに移動させることを好むため、実際には典型的な使用法では、4K の境界に達する前にいくつかのメソッドを呼び出す必要がありますが、これはあまり頻繁には発生しません。しかし、重要な点は、Goroutines は非常に安価に作成できるということです。なぜなら、メモリの割り当てが一度だけ行われ、割り当てられるメモリが非常に小さいからです。新しい Goroutine を作成する際にスタックのサイズを指定する必要はありません。これは非常に良い抽象化であり、スタックのサイズの問題を心配する必要はありません。その後、スタックは必要に応じて増減します。再帰に問題が生じることを心配する必要もなく、大きなキャッシュやプログラマーには完全に見えない何かを心配する必要もありません。すべては Go 言語によって管理されます。これは言語の全体的な考え方です。

27. 自動化の側面について話しましょう。最初に Go 言語をシステムレベルの言語として推進しましたが、興味深い選択はガベージコレクタを使用したことです。しかし、それは速くなく、ガベージコレクションの間隔の問題があります。もしそれを使ってオペレーティングシステムを書くとしたら、非常に厄介です。この問題についてどう考えていますか?#

これは非常に難しい問題だと思います。私たちもまだ解決していません。私たちのガベージコレクタは機能しますが、いくつかの遅延の問題があります。ガベージコレクタは停止する可能性がありますが、私たちの見解は、これは研究課題であり、まだ解決されていないが、努力しているということです。現在の並列マシンにおいて、マシンのコアの一部の断片をバックグラウンドタスクとしてガベージコレクションに専念させることで、並列回収が可能です。この分野には多くの作業があり、多くの成功を収めていますが、非常に微妙な問題です。私は、遅延をゼロにすることはできないと思いますが、できるだけ低く抑えることができると信じています。そうすれば、ほとんどのシステムソフトウェアにとっては問題ではなくなります。すべてのプログラムが顕著な遅延を持たないことを保証するわけではありませんが、成功を収めることができると思います。そして、これは Go 言語の中で比較的活発な分野です。

28. ガベージコレクタに直面せずに済む方法はありますか?例えば、大容量のキャッシュを使用してデータを放り込むことができます。#

Go はメモリのレイアウトに深く入り込むことができ、自分のスペースを割り当てることができます。必要であれば、自分でメモリ管理を行うこともできます。alloc や free メソッドはありませんが、キャッシュを宣言してそこにものを置くことができます。このテクニックは、不要なガベージを生成するのを避けるために使用できます。C 言語のように、C では malloc と free を頻繁に行うとコストが高くなります。したがって、オブジェクトの配列を割り当て、それらをリンクしてリストを形成し、自分のスペースを管理し、malloc や free を使用せずに行うと、速度が非常に速くなります。Go で行っていることと同じことができます。なぜなら、Go はあなたに底層の事物と安全に対処する能力を与えるからです。型システムを欺く必要はなく、実際に自分で行うことができます。

前述のように、Java では、構造体に他のものを埋め込むときは常にポインタを使用しますが、Go ではそれを単一の構造体に置くことができます。したがって、いくつかのキャッシュを必要とするデータ構造がある場合、キャッシュを構造体のメモリに置くことができます。これは効率的であるだけでなく(間接的にキャッシュを取得する必要がないため)、単一の構造体が一度にメモリの割り当てとガベージコレクションを行うことができることを意味します。これにより、オーバーヘッドが減少します。したがって、ガベージコレクションの実際の状況を考慮すると、パフォーマンス要求が高くないものを設計している場合、この問題を常に考慮する必要はありません。しかし、高パフォーマンスが要求される場合、メモリのレイアウトを考慮する必要があります。Go は真のガベージコレクション特性を持つ言語ですが、それでもあなたにどれだけのメモリを制御し、生成されたガベージを管理するためのツールを提供します。これは多くの人が見落としがちなことだと思います。

29. 最後の質問です:Go はシステムレベルの言語ですか、それともアプリケーションレベルの言語ですか?#

私たちはそれをシステムレベルの言語として設計しました。なぜなら、私たちが Google で行っている作業はシステムレベルのものであるからです。Web サーバーやデータベースシステム、ストレージシステムなど、これらはすべてシステムです。しかし、オペレーティングシステムではありません。Go が良いオペレーティングシステム言語になるかどうかはわかりませんが、そうなる可能性がないとも言えません。興味深いことに、私たちが言語を設計する際に採用したアプローチにより、Go は非常に良い汎用言語になりました。これは私たちにとって少し意外でした。私は、ほとんどのユーザーが実際にシステムの観点からそれを考慮していないと思いますが、多くの人が少し Web サーバーや類似のものを作成したことがあります。

Go は多くのアプリケーション関連のものを作成するのにも非常に優れており、より良いライブラリ、ますます多くのツール、そして Go がより役立つものが増えていくでしょう。Go は非常に良い汎用言語であり、私が使った中で最も生産的な言語です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。