Scala 2.8的新特性:Type Specialization(類型專門化)

實在找不到好的名詞來翻譯Type Specialization,不過從實際的情況來說的話,類型專門化還是可以很好的反映出這個新的特性。
 

新特性的起源

要說Type Specialization的起源,還是和泛型的類型擦除和自動裝箱拆箱有關。我們知道,泛型類型擦除後,一般在內部會用它的上界類型來替代,通常是Object。然而,在Java中,基本類型與對象類型是不能相互引用的,這也是爲什麼泛型中不能使用基本類型的原因。所以,如果我們想在泛型中指定基本類型,就要用其對應的對象類型來替代,比如int類型,我們就需要用Integer來替代。這樣就帶來了更大的問題,看看下面的Java代碼:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);

當我們想要將一個int類型1加入到list中時,因爲list是默認ArrayList<Integer>類型的,所以就會發生自動裝箱,將int類型1轉換爲new Integer(1)。要知道這個自動裝箱和拆箱是要損耗性能的,一般是手動轉換的十分之一。所以在有些時候迫使程序員去寫手動的代碼。

Type Specialization就是爲了解決這個問題而產生的。除了生成泛型的一般版本之外,使用Type Specialization還可以讓編譯器爲基本類型生成專門的版本。
 

語法

Type Specialization的語法很簡單,就是在泛型類型前加上註解@specialized。一旦加上@specialized後,編譯器除了生成普通的版本外,還會爲每一個基本類型生成一個對應的版本。Scala的基本類型有Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double九種,那麼編譯器就會生成九種不同的版本。當然,你還可以指定生成對應的版本。

class Vector[@specialized A] {
    def apply(i:Int): A = //...
    def map[@specialized(Int, Boolean) B](f: A=>B) = 
        //...
}

上面的代碼中Vector會將A爲每一個基本類型生成一個專門的版本,而方法map僅僅會生成Int和Boolean的對應版本。
 

實現

Type Specialization是在定義階段執行的。編譯器爲每一個基本類型或者用戶指定的類型生成對應的版本。Type Specialization在定義階段執行可以允許分塊編譯。如果基本類型的專門化在泛型類初始化的時候再執行的話,就有可能導致原始的類定義無法被同時編譯並且編譯器無法將方法專門化。

一個類定義可能生成多個專門化的版本和一個泛型版本(類型擦除的版本)。每一個專門化的類都繼承於泛型的類並且將指定的類型與原始定義綁定。Type Specialization的關鍵點是在程序中同時包含了專門化和類型擦除的類。爲了它們能夠共存,專門化的類能夠替代泛型的類就非常必要了。

讓我們看一下下面的代碼:

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$ // boxing happens here

    protected override def value_=(x$1: Int): Unit =
      value$mcI$sp = x$1

    override def get: AnyRef =
      get$mcI$sp; // boxing happens here

    override def get$mcI$sp: Int = value$mcI$sp;

    override def put(x: AnyRef): Unit =
      put$mcI$sp(x); // boxing happens here

    override def put$mcI$sp(x: Int): Unit = value$mcI$sp = x
}

上面的這個類就是爲類型Int專門化的RefCell類。它包含一個專門化後的整型字段來保存當前的值,重寫了繼承字段的訪問方法,並且用新的訪問方法代替原有的訪問方法。除了字段以外,涉及到泛型的方法也會被重寫。實現的方式就是利用專門化的類型來替代泛型T的引用並且儘可能的使用專門化後的字段和方法。

假設我們的代碼需要使用RefCell來處理Int類型:

object Test extends Application {
    val ref = new RefCell[Int]
    ref.put(10)
    println(ref.get)
}

Scala的編譯器會使用專門化的版本將RefCell[Int]的實例替換掉,這樣在put和get的時候就不會發生自動裝箱拆箱了。
 

成員專門化

這裏需要注意的是,並不是所有的成員都會被專門化。假設我們有一個成員變量m,通常情況下,m至少應該是一個純的專門化類型,或者是專門化類型的數組,這樣纔會被編譯器專門化。例如:

abstract class Foo[@specialized T, U] {
    // the following members are specialized
    def foo1(x: T): U
    def foo2(x: Int): Array[T]
    val a: Array[T]

    // the following members are not specialized
    def bar1(x: U): Unit
    def bar2(x: List[T]): U
    val b: List[T]
}
 
 

當前Type Specialization的狀態

在Scala當前的實現中,默認是啓用Type Specialization的。你可以通過參數-no-specialization來關閉這個特性。
 

標準庫中的Type Specialization

在Scala的標準庫中,以下的class使用了Type Specialization:

  • 兩參數的FunctionN trait。參數的類型被專門化爲Int, Long, Float和Double。結果的類型參數還加入了Unit和Boolean的專門化。
  • AbstractFunctionN和上面的FunctionN一樣也加入了相同的專門化。
  • 兩參數的Tuple被專門化爲Int, Long和Double。
  • 方法Range.foreach被專門化爲Unit,這樣可以加快整數的for循環:for(i<-0 until 100) //...

以上這些大部分翻譯自官方的PDF,有興趣的人可以去看原文。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章