7
1

More than 3 years have passed since last update.

java エスケープ解析とは

Last updated at Posted at 2020-06-09

前書き

Graalを触っていて、部分的エスケープ解析が何のこっちゃ分からなかったから、調べたメモ。

↓Graal・GraalVMに関して書いた記事
https://qiita.com/kinshotomoya/items/39a821dd6a6a52202c0a

サンプルコードは、scalaで書いています。

エスケープとは

オブジェクトの参照がメソッドの外や、違うスレッドに存在すること。
エスケープすると、不特定多数の場所からオブジェクトへ参照できる。

エスケープの条件

実際にのコードでは、どのような場合にオブジェクトがエスケープされていると言えるのか?
主に、以下の3つの条件がある。

  1. メソッドの引数に指定
  2. 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はエスケープされるので、ヒープ領域に格納される。

参考

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1