Scala 2.8 における型専用化
(Type Specialization in Scala 2.8)
1 はじめに
Scala コンパイラは型消去を使って、無視できない程のパフォーマンスコストを引き起こす、パラメータを介する多相性(ジェネリックス)を避けます。基本的に型パラメータは消去され、その上限境界で置き換えられ、デフォルトの Object になります。 その結果、プリミティブ型を含むコードは、適切なボクシングとアンボクシング操作を加えて、オブジェクト上で機能するようになります。 ボクシングされたプリミティブ値を操作するプログラムは、手書きで専用化したコードと比べて 10 倍スローダウンするかもしれません。 これは次に、プログラマーがジェネリックなコレクションを避けて、手書きの専用化バージョンを何度も書くことにつながります。
Scala 2.8 は、専用化された型パラメータを加えます。 専用化された型パラメータはコンパイラに、ジェネリックバージョンの生成と、与えられた定義の多数の専用化バージョンの生成、呼び出し場所で静的な型情報が使えるときはいつでも最も特化した(specific)バージョンを使うこと等を指示します。 専用化機能の詳細な説明は [1] にあります。
2 構文 (Syntax)
メソッドあるいはクラス定義のあらゆる型パラメータに @specialized アノテーションを付加できます。 プログラマは、どのプリミティブ型が専用化されるべき型パラメータであるかをオプションで指示できます。
class Vector[@specialized A] {
def apply(i: Int): A = //..
def map[@specialized(Int, Boolean) B](f: A => B) =
//..
}
上記の Vector は、すべてのプリミティブ型に対して A を専用化します。他方、メソッド map は、Int と Boolean に対してだけ専用化バージョンを得ます。
3 実装 (Implementation)
Scala における専用化は定義場所で実行され、熱心(eager)です。: コンパイラはプリミティブ型のすべての組み合わせ、あるいはユーザーによって定義されたサブセットに対して、専用化された定義を派生させます。 Scala 中のプリミティブ型は、Unit、 Boolean、 Byte、 Short、 Char、 Int、 Long、 Float、 Double です。 分割コンパイルを可能とするために、専用化は定義場所で実行されます。 もし専用化が、ジェネリックスクラスがプリミティブ型にインスタンス化される場所でだけ実行されるなら、オリジナルのクラス定義が同じ実行でコンパイルされないということがあり得ます(専用化はライブラリ中で重要な役割を果たすことが期待されるので、これは通常のケースです)。コンパイラはメソッドの専用化された実装を派生させることができず、人は恩恵を受けられません。
クラス定義は、専用化されたクラスとジェネリックな(型消去された)クラスのセットを生み出します。専用化された各クラスは、型の特化した組合せを使ってオリジナルの定義から派生され、ジェネリッククラスを拡張(extend)します。Scala の専用化の重要な点は、プログラムが専用化と型消去の両方のクラス定義を含むということです。それらを共存させるために、専用化されたインスタンスをジェネリックな対応物の代わりに使える必要があります。
次の例を考えてみます。:
class RefCell[@specialized(Int) T] {
private var value: T = _
def get: T = x
def put(x: T) =
value = x
}
Scalac は RefCell[Int] を拡張するクラスを追加で作りだします。それは、RefCell のメンバーの専用化バージョンを保持します。 また、委譲すべきすべてのジェネリックなメンバー定義をそれらの専用化した定義でオーバライドします。 このコードパスは、ボクシングを含み、型消去を使うこと同じであり、ジェネリックスインターフェースを使うコードが、専用化されたインスタンス使用時に正しく振る舞うことを保証するために必要です。 より多くの型情報が利用可能で、専用化バージョンが存在するコンテキスト中でジェネリックコードを使うときには、コンパイラはメソッド呼び出しを書き直し、そしてクラスインスタンス化を専用化バージョンに書き直します。 このコードパスにより、ボクシングの回避が保証されます。
class RefCell$mcI$sp extends RefCell/*[Int]*/ {
protected var value$mcI$sp: Int = _;
protected override def value: AnyRef =
value$mcI$ // ボクシングがここで起きる
protected override def value_=(x$1: Int): Unit =
value$mcI$sp = x$1
override def get: AnyRef =
get$mcI$sp; // ボクシングがここで起きる
override def get$mcI$sp: Int = value$mcI$sp;
override def put(x: AnyRef): Unit =
put$mcI$sp(x); // ボクシングがここで起きる
override def put$mcI$sp(x: Int): Unit = value$mcI$sp = x
}
上のクラスは Int 型に対して RefCell を専用化します。 それは、現在の値を保持する専用化された整数フィールドを持ち、継承したフィールド(値)のアクセサを代わりにオーバライドして使います。 フィールドに加え、専用化された型パラメータに言及するメソッドもすべてオーバライドします。 それらの実装は、T への参照を Int で置き換え、可能な限りいつでもより特化したフィールドあるいはメソッドを使って書き直します。
このクラスのクライアントがいて、整数を処理するのに使うとします。:
object Test extends Application {
val ref = new RefCell[Int]
ref.put(10)
println(ref.get)
}
Scala コンパイラは、RefCell[Int] のインスタンス化を適切な専用化バージョンで置き換え、そして put や get の呼び出しはボクシングを実行しません。
3.1 メンバーの専用化 (Member Specialization)
クラスのすべてのメンバーが専用化されるわけではありません。 今のところ、メンバー m の専用化された変形は、m の型が少なくとも 1 つの専用化された裸の型パラメータ、あるいは裸の専用化された型パラメータの配列を含む場合だけ、生成されます。例 :
abstract class Foo[@specialized T, U] {
// 次のメンバーは専用化される
def foo1(x: T): U
def foo2(x: Int): Array[T]
val a: Array[T]
// 次のメンバーは専用化されない
def bar1(x: U): Unit
def bar2(x: List[T]): U
val b: List[T]
}
4 状況 (Status)
Scala トランク中の現在の実装は、デフォルトで専用化を使います。 専用化をオフにするには、コマンドライン上で -no-specialization を渡します。
4.1 標準ライブラリの専用化
(Standard Library Specialization)
次のクラスは専用化されています。:
- FunctionN トレイトは 2 パラメータまで。パラメータ型は、Int、 Long 、Float と Double について専用化されます。 結果の型パラメータは、さらに Unit と Boolean について専用化されます。
- AbstractFunctionN は FunctionN と同じ型について専用化されます。
- タプルは 2 パラメータまで。Int、 Long 、Double について専用化されます。
- メソッドRange.foreach は Unit について専用化されます。 これにより、次の形の、整数上の for ループを速くできます。
for (i <- 1 until 100) // ..i..
参考文献 (References)
[1] Dragos, I., and Odersky, M. Compiling generics through user-directed type specialization. In ICOOOLPS '09 :Proceedings of the 4th workshop on the Implementation, Compilation, Optimization of Object-Oriented Languages and Programming Systems (New York, NY, USA, 2009), ACM, pp. 42-47 .
最終更新:2011年04月05日 08:41