web-dev-qa-db-ja.com

Scalaと比較してJava

まず最初に、これが言語X対言語Yの問題ではなく、どちらが良いかを判断することではないことを明確にしたいと思います。

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 ms、Scalaで49 msでした。

41
Giorgio

Scalaではできない、Javaで簡潔かつ効率的に実行できることが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はそれらを最適化しません。それらを回避する必要はありませんが、重要なセクションでは注意してください。 Scalaのforは、メソッド呼び出しと匿名クラスによって実装されることに注意してください。

  • Javaプリミティブに対応するStringArrayAnyValクラスなどのJavaクラスを使用する場合、代替が存在する場合はJavaが提供するメソッドを優先してください。たとえば、lengthではなくStringおよびArraysizeを使用します。

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

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

  • Scalaのほとんどを入手するには、-optimiseと特殊化を使用します。

  • 何が起こっているかを理解する:javapはあなたの友達であり、何が起こっているかを示すScalaフラグの束もそうです。

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

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

39

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

21
World Engineer
  • ScalaでJava/Cのようなコードを記述するだけで、Scalaのパフォーマンスは非常にまともです。コンパイラは、可能な場合、IntCharなどにJVMプリミティブを使用します。 whileループはScalaでも同じくらい効率的です。
  • ラムダ式は、Functionクラスの匿名サブクラスのインスタンスにコンパイルされることに注意してください。ラムダをmapに渡す場合、匿名クラスをインスタンス化する必要があり(一部のローカル変数を渡す必要がある場合があります)、その後、反復ごとにapply呼び出し。
  • _scala.util.Random_などの多くのクラスは、同等のJREクラスの単なるラッパーです。追加の関数呼び出しは少し無駄です。
  • パフォーマンスが重要なコードの暗黙的要素に注意してください。 Java.lang.Math.signum(x)は、RichIntに変換して変換するx.signum()よりもはるかに直接的です。
  • JavaよりもScalaの主なパフォーマンス上の利点は特殊化です。特殊化はライブラリコードで控えめに使用されることに注意してください。
18
Daniel Lubarov
  • 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でこれを行うには、さらに多くの作業を行う必要があります。
5
user unknown