2014-08-31 45 views
12

我遇到了Scala和Java版本之間幾乎相同的性能差異。我看到Java版本比Scala版本快68%。任何想法爲什麼發生這種情況?Scala vs Java性能(HashSet和bigram代)

Java版本:

public class Util { 
public static Set <String> toBigramsJava(String s1) { 
    Set <String> nx = new HashSet <String>(); 
    for (int i = 0; i < s1.length() - 1; i++) { 
     char x1 = s1.charAt(i); 
     char x2 = s1.charAt(i + 1); 
     String tmp = "" + x1 + x2; 
     nx.add(tmp); 
    } 
    return nx; 
} 

}

斯卡拉版本:

object Util { 
def toBigramsScala(str: String): scala.collection.mutable.Set[String] = { 
    val hash: scala.collection.mutable.Set[String] = scala.collection.mutable.HashSet[String]() 
    for (i <-0 to str.length - 2) { 
     val x1 = str.charAt(i) 
     val x2 = str.charAt(i + 1) 
     val tmp = "" + x1 + x2 
     hash.add(tmp) 
    } 
    return hash 
} 

}

測試結果:

scala> Util.time(for(i<-1 to 1000000) {Util.toBigramsScala("test test abc de")}) 17:00:05.034 [info] Something took: 1985ms

Util.time(for(i<-1 to 1000000) {Util.toBigramsJava("test test abc de")}) 17:01:51.597 [info] Something took: 623ms

系統:

我跑這在Ubuntu 14.04,與4個核心和8Gig RAM。 Java版本1.7.0_45,Scala版本2.10.2。

還有一些關於我的blog的更多信息。

+2

雖然這不是問題,但您可以將其修改爲一組匹配的問題和答案。 – 2014-08-31 21:31:47

+1

我建議你看一下字節碼來看看區別。 – 2014-08-31 21:34:56

+1

難道這是對Scala中不存在的Java'for'循環的優化嗎,因爲它們在Scala中有一些特殊性?這兩種方法看起來非常相似。另外,如果用java.util.HashSet替換scala.collection.mutable.HashSet會發生什麼? – Dici 2014-08-31 21:40:46

回答

10

我已經有了大致與此階版本

object Util { 
    def toBigramsScala(str: String) = { 
    val hash = scala.collection.mutable.Set.empty[String] 
    var i: Int = 0 
    while (i < str.length - 1) { 
     val x1 = str.charAt(i) 
     val x2 = str.charAt(i + 1) 
     val tmp = new StringBuilder().append(x1).append(x2).toString() 
     hash.add(tmp) 
     i += 1 
    } 
    hash 
    } 
} 

相同的結果,我記得在斯卡拉)循環呼叫應用(實現的方法上Function0這是megamorphic方法調用(從JVM /昂貴JIT的觀點)。加上可能由javac做的一些字符串連接優化。

我沒有檢查我的假設與查找生成的字節碼,但用 和字符串連接與StringBuilder取代差異可以忽略不計。

Time for Java Version: 451 millis 
Time for Scala Version: 589 millis 
3

我已經進行了類似的測試。

這裏是類:

的Java

public class JavaApp { 
    public static void main(String[] args) { 
     String s1 = args[0]; 
     java.util.Set <String> nx = new java.util.HashSet<>(); 
     for (int i = 0; i < s1.length() - 1; i++) { 
      char x1 = s1.charAt(i); 
      char x2 = s1.charAt(i + 1); 
      String tmp = "" + x1 + x2; 
      nx.add(tmp); 
     } 
     System.out.println(nx.toString()); 
    } 
} 

斯卡拉

object ScalaApp { 
    def main(args:Array[String]): Unit = { 
     var s1 = args(0) 
     val hash: scala.collection.mutable.Set[String] = scala.collection.mutable.HashSet[String]() 
     for (i <-0 to s1.length - 2) { 
      val x1 = s1.charAt(i) 
      val x2 = s1.charAt(i + 1) 
      val tmp = "" + x1 + x2 
      hash.add(tmp) 
     } 
     println(hash.toString()) 
    } 
} 

編譯器和運行時版本

Javac的javac 1.8.0_20-EA

Java Java版本 「1.8.0_20-EA」

Scalac Scala編譯器版本2.11.0 - 版權所有2002年至2013年,LAMP/EPFL

Scala Scala代碼亞軍版本2.11.0 - 版權所有2002年至2013年,LAMP/EPFL

斯卡拉也慢。看看Scala版本,它會創建兩個匿名類。

也可能需要一些時間的一件事是for循環中char變量的auto boxing

44: iload_2 
    45: invokestatic #61     // Method scala/runtime/BoxesRunTime.boxToCharacter:(C)Ljava/lang/Character; 
    48: invokevirtual #55     // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder; 
    51: iload_3 
    52: invokestatic #61     // Method scala/runtime/BoxesRunTime.boxToCharacter:(C)Ljava/lang/Character; 
    55: invokevirtual #55     // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder; 

但是,這並不能解釋一切。

4

對於-推導總是慢然後使用while環或尾遞歸作爲explained here

您示例中的另一個問題是String的串聯。 Scala將使用scala.collection.mutable.StringBuilder,它存在一些性能問題(例如,它會將您的char s設置爲Char實例),如其他答案中所述。

將理解修改爲尾遞歸方法並使用java.lang.StringBuilder在Scala和Java中(在我的機器上Scala實際上會快幾毫秒),您將獲得大致相同的結果。

0

有幾種方法可以進一步加速Scala代碼。

  1. 而不是使用一個StringBuilder,我們改用2個字符的字符數組
  2. 而不是創建臨時瓦爾斯X1和X2,我們只是直接寫入字符數組
  3. 然後,我們使用字符串的字符[]構造函數創建要放置在HashSet中的字符串
  4. 我們將循環終止提取到變量max中,以防JIT錯過優化。

    object Util { 
        def toBigramsScala(str: String) = { 
         val hash = scala.collection.mutable.HashSet.empty[String] 
         val charArray = new Array[Char](2) 
         var i = 0 
         val max = str.length - 1 
         while (i < max) { 
         charArray(0) = str.charAt(i) 
         charArray(1) = str.charAt(i + 1) 
         hash.add(new String(charArray)) 
         i += 1 
         } 
         hash 
        } 
        } 
    

這些變化,我能得到的Java和Scala代碼之間的相同的運行時間。令人驚訝的是(至少在這個例子中),java.util.HashSet沒有提供任何超過mutable.HashSet的性能增益。公平地說,我們也可以將所有這些優化應用於Java代碼,