Javaと比較したScalaのパフォーマンス


41

まず第一に、これはどちらが優れているかを判断するための言語-X-言語-Yの質問ではないことを明確にしたいと思います。

私はJavaを長い間使用しており、Javaを使用し続けるつもりです。これと並行して、私は現在非常に興味を持ってScalaを学んでいます。私の印象に慣れる小さなことは別として、私はこの言語で本当にうまく働くことができるということです。

私の質問は、Scalaで書かれたソフトウェアはJavaで書かれたソフトウェアと比較して、実行速度とメモリ消費量の点でどうですか?もちろん、これは一般的に答えるのが難しい質問ですが、パターンマッチング、高階関数などの高レベルの構成体がオーバーヘッドをもたらすと予想されます。

ただし、Scalaでの私の現在の経験は、コードが50行未満の小さな例に限定されており、これまでベンチマークを実行していません。したがって、実際のデータはありません。

ScalaにJava に対するオーバーヘッドあることが判明した場合、Scala のより複雑な部分とJavaのパフォーマンスに重要な部分をコーディングするScala / Javaプロジェクトを混合するのは理にかなっていますか?これは一般的な習慣ですか?

編集1

私は小さなベンチマークを実行しました。整数のリストを作成し、各整数に2を掛けて新しいリストに入れ、結果のリストを出力します。Java実装(Java 6)とScala実装(Scala 2.9)を作成しました。Ubuntu 10.04でEclipse Indigoで両方を実行しました。

結果は比較可能です:Javaで480ミリ秒、Scalaで493ミリ秒(100回の繰り返しの平均)。ここに私が使用したスニペットがあります。

// Java
public static void main(String[] args)
{
    long total = 0;
    final int maxCount = 100;
    for (int count = 0; count < maxCount; count++)
    {
        final long t1 = System.currentTimeMillis();

        final int max = 20000;
        final List<Integer> list = new ArrayList<Integer>();
        for (int index = 1; index <= max; index++)
        {
            list.add(index);
        }

        final List<Integer> doub = new ArrayList<Integer>();
        for (Integer value : list)
        {
            doub.add(value * 2);
        }

        for (Integer value : doub)
        {
            System.out.println(value);
        }

        final long t2 = System.currentTimeMillis();

        System.out.println("Elapsed milliseconds: " + (t2 - t1));
        total += t2 - t1;
    }

    System.out.println("Average milliseconds: " + (total / maxCount));
}

// Scala
def main(args: Array[String])
{
    var total: Long = 0
    val maxCount    = 100
    for (i <- 1 to maxCount)
    {
        val t1   = System.currentTimeMillis()
        val list = (1 to 20000) toList
        val doub = list map { n: Int => 2 * n }

        doub foreach ( println )

        val t2 = System.currentTimeMillis()

        println("Elapsed milliseconds: " + (t2 - t1))
        total = total + (t2 - t1)
    }

    println("Average milliseconds: " + (total / maxCount))
}

したがって、この場合、Scalaのオーバーヘッド(範囲、マップ、ラムダを使用)は本当に最小限であり、ワールドエンジニアが提供する情報からそれほど遠くないようです。

実行するのに特に重いので、注意して使用する必要がある他のScalaコンストラクトがあるかもしれません。

編集2

あなたの何人かは、内側のループのprintlnが実行時間の大部分を占めることを指摘しました。それらを削除し、リストのサイズを20000ではなく100000に設定しました。結果の平均は、Javaで88ミリ秒、Scalaで49ミリ秒でした。


5
ScalaはJVMバイトコードにコンパイルされるため、理論的には、同じJVMの下で実行されるJavaと同等のパフォーマンスが得られると思います。私が思う違いは、Scalaコンパイラーがバイトコードを作成する方法と、それが効率的に行われる場合です。
maple_shaft

2
@maple_shaft:それとも、Scalaのコンパイル時間にオーバーヘッドがあるのでしょうか?
FrustratedWithFormsDesigner

1
@Giorgio ScalaオブジェクトとJavaオブジェクトにはランタイムの区別はありません。これらはすべて、バイトコードごとに定義され、動作するJVMオブジェクトです。たとえば、言語としてのScalaにはクロージャの概念がありますが、これらがコンパイルされると、バイトコードを使用していくつかのクラスにコンパイルされます。理論的には、まったく同じバイトコードにコンパイルできるJavaコードを物理的に書くことができ、実行時の動作はまったく同じになります。
maple_shaft

2
@maple_shaft:それはまさに私が目指していることです:上記のScalaコードは、対応するJavaコードよりもはるかに簡潔で読みやすいと思います。パフォーマンス上の理由から、Scalaプロジェクトの一部をJavaで記述するのが理にかなっているのか、それらの部分がどうなるのかと思っていました。
ジョルジオ

2
ランタイムは、println呼び出しによって大部分が占有されます。より計算集約的なテストが必要です。
ケビンクライン

回答:


39

Javaでは、Scalaでできないことを簡潔かつ効率的に行うことができることが1つあります。それは列挙です。他のすべてについては、Scalaのライブラリで遅いコンストラクトであっても、Scalaで動作する効率的なバージョンを取得できます。

そのため、ほとんどの場合、コードにJavaを追加する必要はありません。Javaで列挙を使用するコードであっても、Scalaには適切または良い解決策がしばしばあります。余分なメソッドがあり、int定数値が使用される列挙には例外を置きます。

気をつけるべきことは、次のとおりです。

  • ライブラリを強化するパターンを使用する場合は、常にクラスに変換してください。例えば:

    // WRONG -- the implementation uses reflection when calling "isWord"
    implicit def toIsWord(s: String) = new { def isWord = s matches "[A-Za-z]+" }
    
    // RIGHT
    class IsWord(s: String) { def isWord = s matches "[A-Za-z]+" }
    implicit def toIsWord(s: String): IsWord = new IsWord(s)
    
  • コレクションメソッドには注意してください。JVMはほとんどの部分でポリモーフィックであるため、JVMはそれらを最適化しません。それらを避ける必要はありませんが、重要なセクションでは注意を払ってください。forScalaではメソッド呼び出しと匿名クラスを介して実装されることに注意してください。

  • 、などのJavaクラスを使用する場合StringArrayまたはAnyVal代替物が存在する場合、クラスのJavaプリミティブへの対応を、Java(登録商標)によって提供される方法を好みます。たとえば、の代わりにlengthon StringおよびArrayを使用しsizeます。

  • 暗黙の変換を不注意に使用しないでください。設計ではなく誤って変換を使用していることに気付く可能性があります。

  • 特性の代わりにクラスを拡張します。たとえばFunction1、拡張する場合は、AbstractFunction1代わりに拡張します。

  • -optimiseScalaの大部分を取得するための使用と専門化。

  • 何が起きているのかを理解してjavapください:あなたの友人であり、何が起こっているのかを示すScalaフラグもたくさんあります。

  • Scalaのイディオムは、正確性を改善し、コードをより簡潔で保守しやすくするように設計されています。スピードを重視して設計されていないため、クリティカルパスではnullなく使用する必要がある場合は、そうしてくださいOption。Scalaがマルチパラダイムであることには理由があります。

  • パフォーマンスの真の尺度はコードの実行であることを忘れないでください。このルールを無視するとどうなるかについては、この質問をご覧ください。


1
+1:まだ学ばなければならないトピックについても、多くの有用な情報がありますが、それらを見る前にヒントを読んでおくと便利です。
ジョルジオ

最初のアプローチが反射を使用する理由 とにかく匿名クラスを生成するので、リフレクションの代わりにそれを使用しないのはなぜですか?
Oleksandr.Bezhan

@ Oleksandr.Bezhan AnonymousクラスはJavaの概念であり、Scalaの概念ではありません。型の洗練を生成します。基本クラスをオーバーライドしない匿名クラスメソッドには、外部からアクセスできません。同じことはScalaの型の改良にも当てはまらないので、そのメソッドを取得する唯一の方法はリフレクションを使用することです。
ダニエルC.ソブラル

これは非常に恐ろしいことです。特に:「収集メソッドに注意してください-それらはほとんどの部分がポリモーフィックであるため、JVMはそれらを最適化しません。それらを避ける必要はありませんが、重要なセクションでは注意してください。」
マット

21

単一コア、32ビットシステムのベンチマークゲームによると、ScalaはJavaの中央値の80%の速度です。パフォーマンスは、Quad Core x64コンピューターの場合とほぼ同じです。でも、メモリ使用量とコード密度は、ほとんどの場合に非常に似ています。これらの(やや非科学的な)分析に基づいて、ScalaはJavaにオーバーヘッドを追加すると断言するのは正しいと思います。大量のオーバーヘッドを追加するようには見えないので、より多くのスペース/時間を消費するより高次のアイテムの診断が最も正しいと思われます。


2
この回答のためだけ(ヘルプページが示唆するように直接比較を使用してくださいshootout.alioth.debian.org/help.php#comparetwo
igouy

18
  • ScalaでJava / Cのようなコードを書くだけでScalaのパフォーマンスは非常に良くなります。コンパイラは、のためにJVMプリミティブを使用しますIntChar時にそれができる、など。一方、Scalaではループも同様に効率的です。
  • ラムダ式は、Functionクラスの匿名サブクラスのインスタンスにコンパイルされることに注意してください。にlambdaを渡す場合map、匿名クラスをインスタンス化する必要があり(また、一部のローカルを渡す必要がある場合があります)、すべての反復には呼び出しからの追加の関数呼び出しオーバーヘッド(パラメーターを渡す)がありapplyます。
  • のような多くのクラスscala.util.Randomは、同等のJREクラスの単なるラッパーです。追加の関数呼び出しは少し無駄です。
  • パフォーマンスが重要なコードの暗黙的な部分に注意してください。java.lang.Math.signum(x)x.signum()変換されRichIntて戻るよりもはるかに直接的です。
  • Javaに対するScalaの主なパフォーマンス上の利点は、特殊化です。ライブラリコードでは、スペシャライゼーションが控えめに使用されることに注意してください。

5
  • a)限られた知識から、静的mainメソッドのコードはあまり最適化できないことに注意する必要があります。重要なコードを別の場所に移動する必要があります。
  • b)長時間の観測から、パフォーマンステストで大量の出力を行わないことをお勧めします(最適化するのが好きなことを除いて、200万の値を読み取る必要があるのは誰ですか?)。あなたはprintlnを測定していますが、これはあまり面白くないです。printlnをmaxに置き換える:
(1 to 20000).toList.map (_ * 2).max

システムの時間を800ミリ秒から20に短縮します。

  • c)for-comprehensionは少し遅いことが知られています(常に良くなっていることを認めなければなりません)。代わりにwhileまたはtailrecursive関数を使用してください。この例では、外側のループではありません。@ tailrec-annotationを使用して、tairecursivenessをテストします。
  • d)C / Assemblerとの比較が失敗します。たとえば、異なるアーキテクチャ向けにscalaコードを書き直さないでください。歴史的状況とのその他の重要な違いは
    • 入力データに応じて、オンザフライで、そしておそらく動的に最適化するJITコンパイラー
    • キャッシュミスの重要性
    • 並列呼び出しの重要性の高まり。現在、Scalaには、大きなオーバーヘッドなしで並行して動作するソリューションがあります。あなたがもっと多くの仕事をすることを除いて、それはJavaでは不可能です。

2
ループからprintlnを削除しましたが、実際にはScalaコードはJavaコードよりも高速です。
ジョルジオ

Cおよびアセンブラとの比較は、次の意味で行われました。高レベル言語はより強力な抽象化を備えていますが、パフォーマンスのために低レベル言語を使用する必要がある場合があります。この並行性は、Scalaを上位レベルの言語、Javaを下位レベルの言語と見なしていますか?ScalaはJavaと同様のパフォーマンスを提供するようだからです。
ジョルジオ

ClojureやScalaにとってはそれほど重要ではないと思いますが、jRubyとJythonをいじってみたときには、パフォーマンスがより重要なコードをJavaで書いたでしょう。これら2つでは、大きな格差が見られましたが、それは何年も前のことです...
リグ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.