まず最初に、これが言語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でした。
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プリミティブに対応するString
、Array
、AnyVal
クラスなどのJavaクラスを使用する場合、代替が存在する場合はJavaが提供するメソッドを優先してください。たとえば、length
ではなくString
およびArray
でsize
を使用します。
設計ではなく誤って変換を使用していることに気付く可能性があるため、暗黙的な変換の不注意な使用は避けてください。
トレイトの代わりにクラスを拡張します。たとえば、Function1
を拡張する場合は、代わりにAbstractFunction1
を拡張します。
Scalaのほとんどを入手するには、-optimise
と特殊化を使用します。
何が起こっているかを理解する:javap
はあなたの友達であり、何が起こっているかを示すScalaフラグの束もそうです。
Scalaのイディオムは、正確さを改善し、コードをより簡潔かつ保守可能にするように設計されています。これらは速度を考慮して設計されていないため、クリティカルパスでnull
ではなくOption
を使用する必要がある場合は、そうしてください! Scalaがマルチパラダイムであるのには理由があります。
パフォーマンスの真の尺度はコードの実行であることを忘れないでください。そのルールを無視するとどうなるかについての例は この質問 を参照してください。
ベンチマークゲーム によると、シングルコア、32ビットシステムでは、ScalaはJavaと比べて平均80%高速です。パフォーマンスは、ほぼ同じです。クアッドコアx64コンピューター。 メモリ使用量とコード密度 もほとんどの場合非常に似ています。これらの(非科学的)分析に基づいて、ScalaはJavaにいくらかのオーバーヘッドを追加しますが、それは大量のオーバーヘッドを追加するようには見えないので、より多くのスペース/時間を占めるより高次のアイテムの診断が最も正しいと思います。
Int
、Char
などにJVMプリミティブを使用します。 whileループはScalaでも同じくらい効率的です。Function
クラスの匿名サブクラスのインスタンスにコンパイルされることに注意してください。ラムダをmap
に渡す場合、匿名クラスをインスタンス化する必要があり(一部のローカル変数を渡す必要がある場合があります)、その後、反復ごとにapply
呼び出し。scala.util.Random
_などの多くのクラスは、同等のJREクラスの単なるラッパーです。追加の関数呼び出しは少し無駄です。Java.lang.Math.signum(x)
は、RichInt
に変換して変換するx.signum()
よりもはるかに直接的です。(1 to 20000).toList.map (_ * 2).max
システムの時間を800ミリ秒から20に短縮します。