Qiitaの3日目の記事です。2017年12月2日こんな感じでつらいなということになり、アンケートを行いました。2017年12月2日その結果、上記の通りInterfaceとは何なのかの記事を書くことになりました。(異論は認めません)コンパイラ最適化に関しては30%くらい書き進めているのと、Go2についても書き始めてはいるのでどこかで日の目をみることになると思います。(皆さんからの圧力が力になります)導入GoのInterfaceはGoの特徴の一つでInterfaceを制する者はGoを制するとも言われています。Coxによる解説記事を参考にし、2009年の記事のため古くなってしまっている部分を補足しつつInterfaceの正体をさくっと解き明かしていきたいと思います。この記事ではInterfaceの使い方等は取り上げません。いうのが気になる方はぜひtenntennさんによる勉強になるエントリがあるのでそちらを読んでみてください。Goは両者の中間をとったような感じになっており、メソッドテーブルを実行時に検索するというようなことをしています。[src/runtime/iface.go](https://golang.org/src/runtime/iface.go)を見ていきましょう。辺りとかと同じく汚いコードな気がしなくもないですが量が少ない分わりと読めます。インタフェースはインタフェースに格納された型に関する情報へのポインタ関連データへのポインタとして表されています。例としてまず、Stringer型のインタフェースにbを代入したとします。すると、以下の様になります。C)となっているのは昔Cで書かれていた時代の名残でミスなので許してください。1つ目のポインタはitabを参照します。itabは以下の様に定義されています。inter.}itabは型に関するいくつかのメタデータや関数ポインタのリストで構成されます。この例ではStringerインターフェースをを満たすためのメソッドのバイナリ型のリストを保持しています。一方のポインタは実際のデータを参照します。この場合、bのコピーです。bがコピーを作成するのと同じ理由で、bの実体を参照するのではなくbのコピーを作成します。そのため、bを後で変更した場合、sとcは元の値のままになります。インターフェイスに格納された値は任意の大きさまで大きくなる可能性があるのですが、上記の様にすることによってインターフェイス構造体内で値を保持するための記憶領域は少なくて済むので割り当てによってヒープにメモリが割り当てられ、ポインタが記憶されます。(値がスロットに収まるときは明らかに最適化されていますが、後でその点を説明します)インターフェースの呼び出し側は、それが指しているデータの量に関知しません。多くのメソッドを持つインターフェースは、itabの一番下にあるfunリストに多くのデータを保持することになります。itabの計算Goコンパイラまたはリンカが全てのitabを事前計算するのはほぼ不可能です。string)のような具体的な型ごとに型記述構造体を生成します。メタデータの中の型記述構造にはその型によって実装されたmethodのリストが含まれていて、コンパイラは各インタフェースに対してそれぞれ別の型記述構造を生成します。ランタイムは、それを利用してインタフェースのmethod表にリストされている各メソッドを検索して、itabを作成します。ランタイムは生成したitabを生成した後にキャッシュするので、この対応関係を作成するのは一度だけで済むのです。メモリ最適化ここからどのようにメモリ最適化してくのでしょうか。関係するインタフェースの型が空の場合(メソッドがない場合)、itabは削除することができるので値は直接型を指すことができます。インタフェースに関連する値が単一の機械語に収まるサイズの場合は間接割り当てやヒープ割り当てを用いる必要がないため、Binary32をBinaryのように定義してuint32として実装すると、実際の値をインターフェース内に格納することができます。コンパイラは値がインライン化されるかポインタを用いるのかを型のメソッドテーブル(itabにコピーされるもの)にリストされている関数を見て判断します。Binary32).Stringにはなりません。メソッド検索のパフォーマンスJavaScriptやPythonなどはメソッドが呼び出されるたびにメソッドの検索を行います。そのため、多くの場合でパフォーマンスのために単純なキャッシュを使用します。マルチスレッド環境下では複数のスレッドが同時に存在する可能性があるためキャッシュを慎重に管理する必要があり、キャッシュはメモリ競合の原因にもなります。その一方でGoは実行時にメソッドの検索を行うために型のヒントを保持しているため、メソッドの検索を効率的に行うことができます。}Goでは2行目の代入中にitabが計算されるか、キャッシュが適用されます。そのため、4行目で実行されるs.String()呼び出しの際には2回のメモリfetchと1回の間接呼び出し命令が必要です。これとは対照的に、JavaScript、Pythonのような動的言語でのこのプログラムの実装は、ループ内で何度も不要に4行目のメソッド検索を実行します。前述のキャッシュを用いたとしても1回の間接呼び出し命令よりもコストがかかってしまいます。BXLEALはsのアドレスをレジスタBXにロードし、次の2つのMOVL命令は、インタフェースから値を取り出してそれを最初の関数呼び出し引数(SP)にセットします。最後の2つのMOVL命令は、その関数を呼び出すための準備として、itabと関数ポインタをitabから取得します。まとめとこんな感じでとりあえず調べていったのですが、ちょっと時間がなくて調べ足りていないのでマサカリをどんどんください。コンパイラ最適化とGo2については皆さんからの圧力が力になるので圧力の方お願い致します。ランチからでも大丈夫なのでぜひ興味あるかたはTwitterでもWantedlyでもいいので連絡ください。#golang3/20~3/28のコミットから取り上げています。niconegoto注目記事はてなブログをはじめよう!niconegotoさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。