1. 序章

バージョン2.0以降、ScalaコンパイラーにはJVMバイトコードオプティマイザーが含まれており、JVMがより高速に実行できるバイトコードを生成します。 この最適化は、JVM自体によってすでに実行されているすべての最適化に追加されます。 デフォルトでは、コンパイルが遅くなるため、無効になっています。

Scalaオプティマイザによって実行される操作の1つは、インライン化です。 インライン化は、通常は効率を上げるために、関数呼び出しサイトを呼び出し先の本体に置き換える最適化です。

このチュートリアルでは、最初にインライン化を有効にする方法を見てから、Scalaで@inline@noinlineを使用する方法を見ていきます。

2. Scalaでインライン化

Scalaオプティマイザーのインラインの主な目的は、メガモーフィックなコールサイトを回避することです。

コールサイトは、関数を呼び出すコード行です。

def execute(fun: Int => Int)(n: Int): Int = {
  fun(n) // callsite
}

メガモーフィックコールサイトは、3つ以上の関数を呼び出すことができるコード行です。これは、たとえば、関数をパラメーターとして別の関数に渡してから呼び出す場合に発生します。 たとえば、上記の関数 execute は、次の3つ以上の関数を実行できます。

val f1 = {n: Int => n * n}
val f2 = {n: Int => n + n}
val f3 = {n: Int => n}

execute(f1)(5)
execute(f2)(5)
execute(f3)(5)

上記の例では、 f1 f2 、およびf3の3つの異なる関数をexecuteに渡します。 したがって、それは巨大なコールサイトです。

3. @inlineおよび@noinline

Scalaの@inlineと@noinlineは、Scalaオプティマイザーが呼び出しサイトをインライン化する方法を制御する2つのアノテーションです。これらを有効にするには、 -opt:l:inlineフラグを使用してコンパイルする必要があります。 。

インラインを有効にすると、@ inlineアノテーションは、アノテーションが付けられたメソッドまたはコールサイトを常にインライン化しようとするようにコンパイラーに指示します。これがないと、インラインはいくつかのヒューリスティックを適用して、インラインにメソッドを識別します。 この記事では、これらのヒューリスティックがどのように機能するかではなく、上記のアノテーションを使用したときにインライン化がどのように機能するかに焦点を当てます。

一方、 @noinline は、特定のコールサイトにインライン化を適用しないようにインライナーに指示します。

Scalaでのインライン化は、ターゲット関数をfinalとして宣言した場合にのみ可能です。それ以外の場合、サブクラスがアノテーションをオーバーライドする可能性があるため、オプティマイザーはそれをインライン化しません。

3.1. @inlineおよび@noinline関数定義

関数を定義するときにこれらの注釈を使用する方法を見てみましょう。

@inline
final def informalGreeting(name: String): String = s"Hi, $name"

@noinline
final def formalGreeting(name: String): String = s"Hello, $name"

final def veryInformalGreeting(name: String): String = s"Hey, $name"

上記の例では、3つの final 関数、 informalGreeting formalGreeting 、およびveryInformalGreetingを定義しています。 最初のものに@inlineで注釈を付けます。 これは、インライナーが可能な限りコールをインライン化することを意味します。 一方、インライナーは2番目の関数をインライン化することはありません。 最後に、ヒューリスティックに応じて、3番目の関数をインライン化する場合があります。

実際の関数呼び出しでそれを見てみましょう:

def greetBaeldung1 = informalGreeting("Baeldung")
def greetBaeldung2 = formalGreeting("Baeldung")
def greetBaeldung3 = veryInformalGreeting("Baeldung")

greetBaeldung1 は、可能であればインライン化されます。 たとえば、informalGreetingfinalとして宣言しなかった場合、インライン化はできません。 greetBaeldung2がインライン化されることはありません。 greetBaeldung3 は、ヒューリスティックによってはインライン化される場合があります。 繰り返しますが、veryInformalGreetingfinalとして宣言されていない場合、インライン化はできません。

3.2. @inlineおよび@noinline関数呼び出し

関数を呼び出すときに@inlineアノテーションと@noinlineアノテーションをオーバーライドできます。つまり、関数を呼び出すときに、呼び出しをインライン化するかどうかをインライナーに指示できます。 これがどのように機能するか見てみましょう:

def greetBaeldung4 = informalGreeting("Baeldung"): @noinline
def greetBaeldung5 = formalGreeting("Baeldung"): @inline
def greetBaeldung6 = veryInformalGreeting("Baeldung"): @inline
def greetBaeldung7 = veryInformalGreeting("Baeldung"): @noinline

上記の例では、greetBaeldung4informalGreeting@inlineアノテーションをオーバーライドします。 したがって、インライナーがコールをインライン化することはありません。 同様に、 greetBaeldung7 は、veryInformalGreetingの呼び出しをインライン化しないようにコンパイラーに指示します。コールをインライン化します。 繰り返しますが、コンパイラは可能な場合にのみインライン化します。

最後に、より大きな式でコールサイトに注釈を付ける場合は、括弧を使用して、式のどの部分をインラインにするかをインラインに指示する必要があります

def greetBaeldung8 = informalGreeting("Baeldung") + informalGreeting("Baeldung"): @noinline
def greetBaeldung9 = informalGreeting("Baeldung") + (informalGreeting("Baeldung"): @noinline)

この場合、 greetBaeldung8 (informalGreeting( “Baeldung”)+ informalGreeting( “Baeldung”)):@noinline と同等です。つまり、インライナーは最適化をに適用します。全体の表現。 絞り込みたい場合は、greetBaeldung9 のように行う必要があります。ここでは、インライナーは informalGreeting( “Baeldung”)への2回目の呼び出しのみを最適化します。

3.3. ライナーの警告

インライナーは、コールサイトをインライン化できない場合に警告を発行できます。非推奨の警告と同様に、コンパイラーはコンパイルの最後に警告を表示します。 -opt-warningsフラグを使用してそれらを有効にできます。

これらの警告が実際にどのように見えるかを見てみましょう。

class InliningWarning {
  @inline
  def f = 10

  def callsite = f
}

InlineWarning.f は、 final ではないため、インライン化できません。 最初に-opt-warningsなしでコンパイルして、何が起こるかを見てみましょう。

$> scalac InliningWarning.scala -opt:l:inline 
warning: there was one inliner warning; re-run enabling -opt-warnings for details, or try -help one warning found

それでは、-opt-warningsフラグを追加しましょう。

$> scalac InliningWarning.scala -opt:l:inline -opt-warnings
InliningWarning.scala:3: warning: InliningWarning::f()I is annotated @inline but could not be inlined:
The method is not final and may be overridden.
  def callsite = f
                 ^
one warning found

予想どおり、警告は、サブクラスがオーバーライドする可能性があるため、インライナーが f、への呼び出しを最適化できなかったことを示しています。

4. 結論

この記事では、Scalaでインライン化がどのように機能するかを見ました。 オプティマイザーとメガモーフィックコールサイトの問題を理論的に調べました。 次に、Scalaでの@inline@noinlineの使用法を詳しく調べ、関数の定義と呼び出しの両方での動作を分析しました。 最後に、インライン警告がどのように見えるかを確認しました。

いつものように、コードはGitHubから入手できます。