前書き
Graalを触っていて、部分的エスケープ解析が何のこっちゃ分からなかったから、調べたメモ。
↓Graal・GraalVMに関して書いた記事
https://qiita.com/kinshotomoya/items/39a821dd6a6a52202c0a
サンプルコードは、scalaで書いています。
エスケープとは
オブジェクトの参照がメソッドの外や、違うスレッドに存在すること。
エスケープすると、不特定多数の場所からオブジェクトへ参照できる。
エスケープの条件
実際にのコードでは、どのような場合にオブジェクトがエスケープされていると言えるのか?
主に、以下の3つの条件がある。
- メソッドの引数に指定
- returnで返す(戻り値に指定する)
1. メソッドの引数に指定
Objectのインスタンスであるaへの参照は、hogeメソッドを超えて参照できる。
fooメソッドからも参照できる
case class Object()
object Test {
def hoge: Object = {
val a = Object()
foo(a)
}
def foo(obj: Object) = ???
}
2. returnで返す(戻り値に指定する)
Objectのインスタンスbへの参照は、hogeメソッドを超えて参照できる。
b2変数が、オブジェクトaへの参照を持ってる。
case class Object()
object Test {
def hoge: Object = {
val b = Object()
b
}
val b2 = hoge()
}
つまり、メソッドを超えて上記の例のインスタンス、a・bへの参照が可能であるということ。
エスケープ解析とは
インスタンスの参照がメソッド外・別のスレッドにエスケープするかどうかを解析する。
様々なアルゴリズムがある。
エスケープされていないと解析されると?
エスケープ解析の結果、参照がメソッド内に閉じられているとわかると、
1. インスタンスをヒープではなく、スタックを格納する。
メソッド内のみでインスタンスが使用されるなら、メソッド終了後に解放されるスタック領域に格納すると効果的。
2. メソッド実行の際に、不要な同期を無視する
単一スレッドからのみの参照ならば、スレッド間の同期は不要になる。
最適化例
エスケープ解析の結果、コンパイラはコードを最適化する。
その例を示す。
インライン化
以下のようなコードがある。
class Person {
def get(name: String) = {
val person: Person = Person(name)
if (person.name === "hoge") {
}
...
}
}
case class Person(name: String)
Personオブジェクトのインスタンスである、personはエスケープされていない。
このような場合、コンパイラはインライン化して最適化する。
class Person {
def get(name: String) = {
// val person: Person = Person(name)
if (name === "hoge") { // インライン化
}
...
}
}
case class Person(name: String)
部分的エスケープ解析
新しいJITコンパイラ Graalの特徴の1つである。
部分的に最適化をすることができる。
以下のようなコードがあった場合。
class Person {
def get(name: String) = {
val person: Person = Person(name)
val cachePerson = Cache.get(name)
if (cachePerson.isDefined) {
cachePerson
} else {
addToCache(person)
person
}
}
}
case class Person(name: String)
cachePersonが存在している場合、オブジェクトpersonはエスケープされない。
そのことをコンパイラは解析して、以下のようなコードに直す。
class Person {
def get(name: String) = {
val cachePerson = Cache.get(name)
if (cachePerson.isDefined) {
cachePerson
} else {
val person: Person = Person(name)
addToCache(person)
person
}
}
}
case class Person(name: String)
val person: Person = Person(name)
をelse
配下に移動し、cachePerson
が存在する場合には、ヒープ領域へのメモリ割り当てををなくすことができる。代わりにスタック領域に格納され、処理効率よくなる。
cachePerson
が存在しなかった場合には、personはエスケープされるので、ヒープ領域に格納される。
参考
- https://www.slideshare.net/jyukutyo/graal-in-graalvm-a-new-jit-compiler
- https://docs.oracle.com/javase/jp/11/vm/java-hotspot-virtual-machine-performance-enhancements.html#GUID-6BD8FCB5-995B-4AE9-BFAA-B2C7DE2BA5CD
- https://www.weblio.jp/wkpja/content/%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E8%A7%A3%E6%9E%90_%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E8%A7%A3%E6%9E%90%E3%81%AE%E6%A6%82%E8%A6%81
- https://gist.github.com/kawasima/32b8097163029d452553e4ae5b8e070b