2017-01-28 3 views
3

多くの言語では、ローカル変数は、それらが関数呼び出しを超えて生きなければならないので、それらが作成され、JavaScriptの/ PythonでGO - エスケープ解析

コールスタック、変数はヒープに配置されているだけ閉鎖に位置しています。 GOで


、(スライスタイプ[]intなど)いくつかのGOタイプのJavaScript/Pythonのような、メモリの他の部分を参照して行います。

GOでは、すべてのタイプの変数がJavascript/Pythonのように参照を保持するわけではありません。例えば

1)はCをより制御

ため、C構文&b[index]を使用して、各アレイ素子の位置のアクセスを取得することを可能にする以外[3]int型可変b直接、Cのように、int「sのアレイを記憶します

2)intタイプ変数cは、Cのように値がintであることを直接的に格納します。ただし、Cは位置情報アクセスを取得するための構文(&c)を提供することにより、 GOで


ローカル変数はヒープ/スタック上にあるため、私の理解では、ある(下記)のコード例では、コンパイラのエスケープ解析の適用に依存し、

func foo() []int { 
    // the array lives beyond the call to foo in which it is created 
    var a [5]int 
    return a[:] // range operator 
} 

ことをコンパイラに伝えています変数aはスコープを超えているため、ヒープには割り当てますが、スタックには割り当てません。


質問:

は、変数はヒープに割り当てられますaていますか?

+0

すべての言語がCのように動作するわけではないことに注意してください.JavaScriptやlispなどの多くの言語では、クロージャは実際にスタックの一部です。言語のスタックはCの意味では "スタック"に実装されていませんが、スタックはリンクされたリストとして実装されています。つまり、一部の言語のスタックがヒープに実装されています。 Javascriptの実装では、古典的な関数のリンクリストスタックや、囲まれたフリー変数を取り込むための何らかの種類のクロージャ発見メカニズムを実装することができます。どちらも同じように動作します。 – slebetman

+0

ここでは、RubyのインタプリタをRubyに書く上で、関数型言語でのクロージャの伝統的な実装について説明しています。http://hokstad.com/how-to-implement-closures彼の説明 '伝統的なスタックの代わりに、ヒープ上の関数/メソッド呼び出しのアクティベーションフレーム(引数とローカル変数)をリンクリストとして置く - ヒープ内のインタープリタのスタックを実装している – slebetman

答えて

7

Goでは、最適な決定を下すためにコンパイラを信頼することになっています。可能であれば、スタックにメモリを割り当てます。 the FAQ

正確性の観点から、あなたは知る必要はありません。 Goの各変数は、参照がある限り存在します。実装によって選択された格納場所は、言語のセマンティクスとは無関係です。

保存場所は効率的なプログラムの作成に影響します。可能であれば、Goコンパイラはその関数のスタックフレーム内の関数にローカルな変数を割り当てます。ただし、コンパイラが関数が返された後に変数が参照されないことを証明できない場合、コンパイラはポインタエラーが発生しないように、ガーベジコレクションされたヒープに変数を割り当てる必要があります。また、ローカル変数が非常に大きい場合は、スタックではなくヒープに格納する方が意味があります。

現在のコンパイラでは、変数のアドレスが取得されている場合、その変数はヒープ上の割り当ての候補になります。しかし、基本的なエスケープ分析では、その変数が関数からの復帰を過ぎても生き残らず、スタックに常駐できるいくつかのケースが認識されます。最適化なし


(インライン化)、はいaは、ヒープに割り当てられます。我々は-gcflags='-m'https://play.golang.org/p/l3cZFK5QHO)を渡すことによって、エスケープ分析を確認することができます。

私たちは、コンパイラがあるため、彼らは両方の脱出、ヒープ上のライン10上の5行目 inlined.ano_inline.bを割り当てることを決めたことがわかり
$ nl -ba 1.go 
    1 package main 
    2 
    3 func inlined() []int { 
    4  var a [5]int 
    5  return a[:] 
    6 } 
    7 
    8 //go:noinline 
    9 func no_inline() []int { 
    10  var b [5]int 
    11  return b[:] 
    12 } 
    13 
    14 func main() { 
    15  var local_array [5]int 
    16  var local_var int 
    17  println(no_inline()) 
    18  println(inlined()) 
    19  println(local_array[:]) 
    20  println(&local_var) 
    21 } 
$ go build -gcflags='-m' 1.go 
# command-line-arguments 
./1.go:3: can inline inlined 
./1.go:18: inlining call to inlined 
./1.go:5: a escapes to heap 
./1.go:4: moved to heap: a 
./1.go:11: b escapes to heap 
./1.go:10: moved to heap: b 
./1.go:18: main a does not escape 
./1.go:19: main local_array does not escape 
./1.go:20: main &local_var does not escape 

その範囲。

しかし、インライン化後、コンパイラはaがそれ以上エスケープしないことに気づいたので、変数をスタックに再度割り当てることができると判断します(行18)。

その結果、変数amainのゴルーチンのスタックに割り当てられ、変数bはヒープに割り当てられます。出力からわかるように、bのアドレスは0x1043xxxxにあり、その他はすべて0x1042xxxxにあります。

$ ./1 
[5/5]0x10432020 
[5/5]0x10429f58 
[5/5]0x10429f44 
0x10429f40 
関連する問題