Javaでプリミティブを保持する場合

プリミティブは、1996年の最初のリリース以来Javaプログラミング言語の一部でしたが、それでもなお、より物議を醸す言語機能の1つです。John Mooreは、プリミティブがある場合とない場合の両方で、単純なJavaベンチマークを比較することにより、Java言語でプリミティブを保持することを強く主張しています。次に、特定のタイプのアプリケーションでJavaのパフォーマンスをScala、C ++、およびJavaScriptのパフォーマンスと比較します。ここで、プリミティブは顕著な違いをもたらします。

質問:不動産を購入する上で最も重要な3つの要素は何ですか?

回答:場所、場所、場所。

この古くてよく使われる格言は、不動産に関しては、場所が他のすべての要素を完全に支配していることを意味します。同様の議論で、Javaでプリミティブ型を使用するために考慮すべき3つの最も重要な要素は、パフォーマンス、パフォーマンス、パフォーマンスです。不動産の議論とプリミティブの議論には2つの違いがあります。まず、不動産の場合、ほとんどすべての状況で場所が支配的ですが、プリミティブ型を使用することによるパフォーマンスの向上は、アプリケーションの種類によって大きく異なります。第二に、不動産に関しては、場所と比較して通常はマイナーですが、考慮すべき他の要因があります。プリミティブ型の場合、それらを使用する理由は1つだけです。パフォーマンスです。; そして、アプリケーションがそれらの使用から利益を得ることができる種類である場合にのみ。

プリミティブは、バックエンドにデータベースを備えたクライアントサーバープログラミングモデルを使用するほとんどのビジネス関連およびインターネットアプリケーションにほとんど価値を提供しません。ただし、数値計算が支配的なアプリケーションのパフォーマンスは、プリミティブを使用することで大きなメリットが得られます。

Javaにプリミティブを含めることは、この決定に関連する記事やフォーラムの投稿の数からも明らかなように、より物議を醸す言語設計の決定の1つです。Simon Ritterは、2011年11月のJAX Londonの基調講演で、Javaの将来のバージョンでのプリミティブの削除が真剣に検討されていると述べました(スライド41を参照)。この記事では、プリミティブとJavaのデュアル型システムを簡単に紹介します。コードサンプルと簡単なベンチマークを使用して、特定の種類のアプリケーションにJavaプリミティブが必要な理由を説明します。また、JavaのパフォーマンスをScala、C ++、およびJavaScriptのパフォーマンスと比較します。

ソフトウェアパフォーマンスの測定

ソフトウェアのパフォーマンスは通常、時間とスペースの観点から測定されます。時間は、3.7分などの実際の実行時間、またはOn 2)などの入力のサイズに基づく成長の順序にすることができます。スペースパフォーマンスについても同様の指標が存在します。これは、メインメモリの使用量で表されることがよくありますが、ディスクの使用量にも拡張できます。パフォーマンスの改善には通常、時間と空間のトレードオフが伴います。時間の改善のための変更は、多くの場合、空間に悪影響を及ぼし、その逆も同様です。成長順序の測定はアルゴリズムに依存し、ラッパークラスからプリミティブに切り替えても結果は変わりません。しかし、実際の時間と空間のパフォーマンスに関しては、ラッパークラスの代わりにプリミティブを使用すると、時間と空間の両方が同時に改善されます。

プリミティブとオブジェクト

この記事を読んでいるかどうかはすでにご存知でしょうが、Javaには、通常はプリミティブ型とオブジェクト型と呼ばれるデュアル型システムがあり、単にプリミティブとオブジェクトと略されることがよくあります。 Javaには8つのプリミティブ型が事前定義されており、それらの名前は予約済みのキーワードです。一般に使用される例としてはintdouble、およびboolean。基本的に、すべてのユーザー定義型を含むJavaの他のすべての型は、オブジェクト型です。 (配列型は少しハイブリッドであるため、「本質的に」と言いますが、プリミティブ型よりもオブジェクト型に非常に似ています。)各プリミティブ型には、オブジェクト型である対応するラッパークラスがあります。例には、Integerfor intDoublefor double、およびBooleanforが含まれbooleanます。

プリミティブ型は値ベースですが、オブジェクト型は参照ベースであり、その中にはプリミティブ型の力と論争の原因の両方があります。違いを説明するために、以下の2つの宣言を検討してください。最初の宣言はプリミティブ型を使用し、2番目の宣言はラッパークラスを使用します。

 int n1 = 100; Integer n2 = new Integer(100); 

JDK 5に追加された機能であるオートボクシングを使用すると、2番目の宣言を単純に短縮できます。

 Integer n2 = 100; 

しかし、基礎となるセマンティクスは変更されません。オートボクシングはラッパークラスの使用を簡素化し、プログラマーが記述しなければならないコードの量を減らしますが、実行時に何も変更しません。

プリミティブn1オブジェクトn2とラッパーオブジェクトの違いは、図1の図に示されています。

ジョン・I・ムーア・ジュニア

変数n1は整数値を保持しますが、変数にn2はオブジェクトへの参照が含まれており、整数値を保持するのはオブジェクトです。さらに、によって参照されるオブジェクトn2には、クラスオブジェクトへの参照も含まれますDouble

プリミティブの問題

プリミティブ型の必要性を納得させる前に、多くの人が私に同意しないことを認めなければなりません。 「有害と見なされるプリミティブ型」のShermanAlpertは、プリミティブは「手続き型セマンティクスを他の点では均一なオブジェクト指向モデルに混合するため、有害である」と主張しています。プリミティブはファーストクラスのオブジェクトではありませんが、主にファーストクラスのオブジェクトを含む言語で存在します。クラスオブジェクト。」プリミティブとオブジェクト(ラッパークラスの形式)は、論理的に類似した型を処理する2つの方法を提供しますが、基本的なセマンティクスは大きく異なります。たとえば、2つのインスタンスが等しいかどうかをどのように比較する必要がありますか?プリミティブ型の場合は==演算子を使用しますが、オブジェクトの場合は、を呼び出すことをお勧めします。equals()メソッド。これはプリミティブのオプションではありません。同様に、値を割り当てたりパラメータを渡したりするときは、異なるセマンティクスが存在します。デフォルト値でさえ異なります。たとえば、0forintnullfor Integer

この問題の背景については、EricBrunoのブログ投稿「Amodernprimitivediscussion」を参照してください。このブログ投稿では、プリミティブの長所と短所のいくつかを要約しています。Stack Overflowに関する多くの議論は、「なぜ人々はまだJavaでプリミティブ型を使用しているのか」など、プリミティブにも焦点を当てています。および「プリミティブの代わりに常にオブジェクトを使用する理由はありますか?」プログラマーのStackExchangeは、「Javaでプリミティブとクラスをいつ使用するか」というタイトルの同様のディスカッションを主催しています。

メモリ使用率

doubleJavaのAは常にメモリ内で64ビットを占有しますが、参照のサイズはJava仮想マシン(JVM)によって異なります。私のコンピューターは64ビットバージョンのWindows7と64ビットJVMを実行しているため、コンピューター上の参照は64ビットを占有します。図1の図に基づいて私は、単一の期待doubleなどのn18バイト(64ビット)を占めるように、私は、単一の期待Doubleなどn2のために、オブジェクトへの参照のために8 8から24バイトを占有するdoubleに格納されている値オブジェクト、およびのクラスオブジェクトへの参照用の8 Double。さらに、Javaは追加のメモリを使用して、オブジェクト型のガベージコレクションをサポートしますが、プリミティブ型はサポートしません。それをチェックしよう。

「Javaプリミティブ型とラッパー」のGlenMcCluskeyと同様のアプローチを使用して、リスト1に示す方法で、のn行n列の行列(2次元配列)が占めるバイト数を測定しますdouble

リスト1.タイプdoubleのメモリー使用率の計算

 public static long getBytesUsingPrimitives(int n) { System.gc(); // force garbage collection long memStart = Runtime.getRuntime().freeMemory(); double[][] a = new double[n][n]; // put some random values in the matrix for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) a[i][j] = Math.random(); } long memEnd = Runtime.getRuntime().freeMemory(); return memStart - memEnd; } 

Modifying the code in Listing 1 with the obvious type changes (not shown), we can also measure the number of bytes occupied by an n-by-n matrix of Double. When I test these two methods on my computer using 1000-by-1000 matrices, I get the results shown in Table 1 below. As illustrated, the version for primitive type double equates to a little more than 8 bytes per entry in the matrix, roughly what I expected. However, the version for object type Double required a little more than 28 bytes per entry in the matrix. Thus, in this case, the memory utilization of Double is more than three times the memory utilization of double, which should not be a surprise to anyone who understands the memory layout illustrated in Figure 1 above.

Table 1. Memory utilization of double versus Double

Version Total bytes Bytes per entry
Using double 8,380,768 8.381
Using Double 28,166,072 28.166

Runtime performance

To compare the runtime performances for primitives and objects, we need an algorithm dominated by numerical calculations. For this article I have chosen matrix multiplication, and I compute the time required to multiply two 1000-by-1000 matrices. I coded matrix multiplication for double in a straightforward manner as shown in Listing 2 below. While there may be faster ways to implement matrix multiplication (perhaps using concurrency), that point is not really relevant to this article. All I need is common code in two similar methods, one using the primitive double and one using the wrapper class Double. The code for multiplying two matrices of type Double is exactly like that in Listing 2 with the obvious type changes.

Listing 2. Multiplying two matrices of type double

 public static double[][] multiply(double[][] a, double[][] b) { if (!checkArgs(a, b)) throw new IllegalArgumentException("Matrices not compatible for multiplication"); int nRows = a.length; int nCols = b[0].length; double[][] result = new double[nRows][nCols]; for (int rowNum = 0; rowNum < nRows; ++rowNum) { for (int colNum = 0; colNum < nCols; ++colNum) { double sum = 0.0; for (int i = 0; i < a[0].length; ++i) sum += a[rowNum][i]*b[i][colNum]; result[rowNum][colNum] = sum; } } return result; } 

I ran the two methods to multiply two 1000-by-1000 matrices on my computer several times and measured the results. The average times are shown in Table 2. Thus, in this case, the runtime performance of double is more than four times as fast as that of Double. That is simply too much of a difference to ignore.

Table 2. Runtime performance of double versus Double

Version Seconds
Using double 11.31
Using Double 48.48

The SciMark 2.0 benchmark

これまで、行列乗算の単一の単純なベンチマークを使用して、プリミティブがオブジェクトよりも大幅に優れたコンピューティングパフォーマンスを実現できることを示してきました。私の主張を補強するために、より科学的なベンチマークを使用します。SciMark 2.0は、米国国立標準技術研究所(NIST)から入手できる科学および数値計算用のJavaベンチマークです。このベンチマークのソースコードをダウンロードして、プリミティブを使用した元のバージョンとラッパークラスを使用した2番目のバージョンの2つのバージョンを作成しました。第二版のために私は交換しintIntegerdoubleしてDoubleラッパークラスを使用しての完全な効果を得るために。どちらのバージョンも、この記事のソースコードで入手できます。

ベンチマークJavaのダウンロード:ソースコードをダウンロードしますJohn I. Moore、Jr。

The SciMark benchmark measures performance of several computational routines and reports a composite score in approximate Mflops (millions of floating point operations per second). Thus, larger numbers are better for this benchmark. Table 3 gives the average composite scores from several runs of each version of this benchmark on my computer. As shown, the runtime performances of the two versions of the SciMark 2.0 benchmark were consistent with the matrix multiplication results above in that the version with primitives was almost five times faster than the version using wrapper classes.

Table 3. Runtime performance of the SciMark benchmark

SciMark version Performance (Mflops)
Using primitives 710.80
Using wrapper classes 143.73

You've seen a few variations of Java programs doing numerical calculations, using both a homegrown benchmark and a more scientific one. But how does Java compare to other languages? I'll conclude with a quick look at how Java's performance compares to that of three other programming languages: Scala, C++, and JavaScript.

Benchmarking Scala

ScalaはJVMで実行されるプログラミング言語であり、人気が高まっているようです。Scalaは統一された型システムを持っています。つまり、プリミティブとオブジェクトを区別しません。Scalaの数値型クラス(Pt。1)のErik Osheimによると、Scalaは可能な場合はプリミティブ型を使用しますが、必要に応じてオブジェクトを使用します。同様に、Martin OderskyによるScalaの配列の説明によると、「... Scala配列Array[Int]はJavaとして表されint[]、anArray[Double]はJavaとして表されますdouble[]...」

それで、これは、Scalaの統合型システムがJavaのプリミティブ型に匹敵する実行時パフォーマンスを持つことを意味しますか?どれどれ。