1. 概要

この短いチュートリアルでは、 @switch アノテーションがScalaで何であるかを、関連する例を使用してそのセマンティクスとユースケースとともに学習します。 また、さまざまな状況でのコンパイラの動作と、それに関連するパフォーマンスへの影響についても説明します。

2. Scalaの@switchアノテーションとは何ですか?

Scalaコンパイラーは、Java のswitchステートメントのように扱うことにより、パターンマッチングの特定のパフォーマンス最適化を行います。 コンパイラは、パターンマッチをブランチテーブルにコンパイルすることで最適化するため、複数のifステートメントを使用する単純な決定木よりもはるかに高速になります。

@switchアノテーションをScalaのmatchステートメントに適用できます。 @switchアノテーションを使用して、コンパイラがパターン一致式に最適化を適用することを確認できます。 コンパイラーは、最適化を実行できない場合、警告メッセージを発行します。

3. コンパイラの動作とパフォーマンスへの影響

match 式は、 tableswitch、 lookupswitch、、または決定木(複数の if ステートメントのチェーン)の3つの異なる方法でコンパイルできます。 。

tableswitchlookupswitchはどちらも、ブランチテーブルを使用して case 値を格納し、ラベルに基づいて一致する値を選択します。 Scalaコンパイラーは、 case ステートメントの値がどれだけ連続しているかに基づいて、どちらを使用するかを決定します。 これらの操作には、それぞれ O(1) O(log n)の時間計算量があります。

3.1. テーブルスイッチ

これは、2つの主要で好ましい最適化方法です。 ラベル付きの値のテーブルを使用して、対応する値を選択します 。 次に、これらのラベルは計算された直接ジャンプに使用されます。 したがって、は定数時間のO(1)操作であり、lookupswitchよりも高速な最適化です。 この最適化を行うには、caseステートメントの値が連続している必要があります

case値がギャップのない連続整数であるパターンマッチングの例を見てみましょう。

val numWheels = 2

(numWheels : @switch) match {
  case 1 => println("MonoWheeler")
  case 2 => println("TwoWheeler")
  case 3 => println("ThreeWheeler")
  case 4 => println("FourWheeler")
  case _ => println("UnKnown")
}

javap コマンドを使用してクラスファイルを分解し、バイトコードを確認してみましょう。

16: tableswitch   { // 1 to 6
               1: 56
               2: 67
               3: 78
               4: 89
               5: 111
               6: 100
         default: 111
    }

コンパイラーが期待どおりに最適化を適用したことがわかります。

3.2. ルックアップスイッチ

case ステートメントの値が連続しておらず、の間にギャップがある場合、コンパイラーはtableswitchの代わりにlookupswitchを使用します。 ここで、コンパイラはソートされたキーとラベルを検索に使用します。 次に、は、ソートされたキーにバイナリ検索を適用して、一致する値を見つけます。 したがって、これはO(log n)操作です。

連続していないchar値のパターンマッチングの例を見てみましょう。

val alphabet = 'A'

(alphabet : @switch) match {
  case 'A' => println("ANIMAL")
  case 'E' => println("ELEPHANT")
  case 'I' => println("IRON")
  case 'O' => println("OWL")
  case 'U' => println("UMBRELLA")
  case _ => println("Not a Vowel")
}

バイトコードをもう一度見てみましょう。

12: lookupswitch  { // 5
              65: 64
              69: 75
              73: 86
              79: 97
              85: 108
         default: 119
    }

今回は、コンパイラがtableswitchの代わりにlookupswitchを使用していることを確認します。

4. @switch最適化を適用するために必要な条件

いくつかの簡単な例で、@switchがどのように最適化につながるかを見てきました。 ただし、これらの最適化にはいくつかの注意点があります。 Scalaコンパイラーは、以下の条件が満たされた場合にのみ@switch最適化を適用します。

  • matchステートメントのすべてのcase値は、整数型、または char short byteなどの整数に安全に収まる型である必要があります。 、およびboolShortおよびByteは、次のポイントで説明するタイプチェックの制限のため、リストから除外されます。
  • match式には、タイプチェック、ifステートメント、またはエクストラクタを含めることはできません。
  • 式は、参照値ではなくリテラル値である必要があります。 計算値であってはならず、コンパイル時に使用可能である必要があります。
  • 3つ以上のcaseステートメントが存在する必要があります case ステートメントが2つ以下の場合、パフォーマンス上の利点はありません。

参照値を使用して@switchアノテーションをmatchステートメントに適用するときのコンパイラーの動作を観察してみましょう。

val gender = 1
val male = 0
val female = 1

(gender : @switch) match {
  case `male` => println("Male")
  case `female` => println("Female")
  case _ => println("Invalid")
}

コンパイラから、@switch最適化を発行できなかったことを示す警告メッセージが表示されます。

NotOptimizedExample.scala:10: warning: could not emit switch for @switch annotated match
  (gender : @switch) match {    // Compiler gives a warning: could not emit switch for @switch annotated match
             ^
one warning found

5. 結論

このチュートリアルでは、Scalaの @switch アノテーションが、コンパイラーがパターン一致ステートメントを最適化するのにどのように役立つかを見てきました。 テーブルスイッチまたはルックアップスイッチを使用することで得られるパフォーマンスの向上を学びました。

次に、コンパイラーが最適化を適用せず、単純な分岐ステートメントを使用するすべてのケースをリストしました。

いつものように、完全なソースコードはGitHubにあります。