Javaでプリミティブを保持するためのケース

プリミティブは、1996年の最初のリリース以来、Javaプログラミング言語の一部となっていますが、依然として議論の余地のある言語機能の1つです。 John Moore氏は、プリミティブの有無にかかわらず、単純なJavaベンチマークを比較することにより、Java言語でプリミティブを保持するための強力なケースを作 彼は次に、プリミティブが顕著な違いを生む特定のタイプのアプリケーションにおけるJavaのパフォーマンスとScala、C++、JavaScriptのパフォーマンスを比較します。

: 不動産の購入の3つの最も重要な要因は何であるか。
答え:場所、場所、場所。

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

プリミティブは、バックエンド上のデータベースを持つクライアントサーバプログラミングモデルを使用するほとんどのビジネス関連およ しかし、数値計算によって支配されるアプリケーションの性能は、プリミティブの使用から大きく利益を得ることができます。

Javaにプリミティブを含めることは、この決定に関連する記事やフォーラムの投稿の数によって証明されるように、より論争の的になっている言語設計の決定の一つとなっている。 Simon Ritter氏は、2011年11月のJax Londonの基調講演で、Javaの将来のバージョンでプリミティブを削除することを真剣に検討していることを指摘した(スライド41参照)。 この記事では、プリミティブとJavaのdual-typeシステムを簡単に紹介します。 コードサンプルと簡単なベンチマークを使用して、特定のタイプのアプリケーションにJavaプリミティブが必要な理由を説明します。 また、JavaのパフォーマンスをScala、C++、JavaScriptのパフォーマンスと比較します。

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

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

プリミティブ型は値に基づいていますが、オブジェクト型は参照に基づいており、プリミティブ型の力と論争の源の両方があります。 違いを説明するために、以下の2つの宣言を考えてみましょう。 最初の宣言ではプリミティブ型を使用し、2番目の宣言ではラッパークラスを使用します。JDK5に追加された機能であるautoboxingを使用すると、2番目の宣言を単純に

Integer n2 = 100;

に短縮できますが、基本的なセマンティクスは変更されません。 自動ボックス化は、ラッパークラスの使用を簡素化し、プログラマが記述する必要があるコードの量を減らしますが、実行時には何も変更しません。

プリミティブn1とラッパーオブジェクトn2の違いを図1の図で示します。

John I.Moore,Jr.

図1. プリミティブとオブジェクトのメモリレイアウト

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

プリミティブの問題

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

この問題の詳細な背景については、Eric Brunoのブログ記事”a modern primitive discussion”を参照してください。 Stack Overflowに関する多くの議論は、”なぜ人々はまだJavaでプリミティブ型を使用するのですか?”and”プリミティブの代わりに常にオブジェクトを使用する理由はありますか?.”プログラマスタックExchangeは、javaでプリミティブvsクラスを使用するとき”と題する同様の議論をホストしますか?”.

メモリ使用率

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

“Javaプリミティブ型との比較”のGlen McCluskeyと同様のアプローチを使用します。 リスト1に示す方法は、doubleのn行n列の行列(2次元配列)が占めるバイト数を測定します。

リスト1. Double

型のメモリ使用率の計算リスト1のコードの変更明らかな型の変更(図示せず)では、n行n列の行列Doubleが占めるバイト数を測定することもできます。 私は1000行1000列の行列を使用して自分のコンピュータ上でこれらの二つの方法をテストすると、私は以下の表1に示す結果を取得します。 図に示すように、プリミティブ型doubleのバージョンは、行列のエントリごとに8バイトを少し超えるものに相当しますが、大体私が期待していたものです。 ただし、オブジェクトタイプDoubleのバージョンでは、マトリックス内のエントリごとに28バイトを少し超えている必要がありました。 したがって、この場合、Doubleのメモリ使用率はdoubleのメモリ使用率の3倍以上であり、上の図1に示されているメモリレイアウトを理解している人には驚

実行時のパフォーマンス

プリミティブとオブジェクトの実行時のパフォーマンスを比較するには、数値計算によって支配されるアルゴリズムが必 この記事では、行列の乗算を選択し、2つの1000行1000列の行列を乗算するのに必要な時間を計算します。 以下のリスト2に示すように、doubleの行列乗算を簡単な方法でコード化しました。 行列乗算を実装するより高速な方法があるかもしれませんが(おそらく並行性を使用して)、その点はこの記事には実際には関係ありません。 私が必要とするのは、プリミティブdoubleを使用し、ラッパークラスDoubleを使用する2つの同様のメソッドの共通コードだけです。 タイプDoubleの2つの行列を乗算するコードは、明らかにタイプが変更されているリスト2のコードとまったく同じです。

リスト2。 タイプdouble

の2つの行列を乗算する2つの方法を実行して、コンピューターで2つの1000行1000列の行列を数回乗算し、結果を測定しました。 平均時間を表2に示す。 したがって、この場合、doubleの実行時パフォーマンスはDoubleの4倍以上の高速です。 それは無視するにはあまりにも多くの違いです。

SciMark2.0ベンチマーク

これまでのところ、行列乗算の単一の単純なベンチマークを使用して、プリミティブがオブジェクトよりも大幅に高い計算性能を 私の主張を強化するために、私はより科学的なベンチマークを使用します。 SciMark2.0は、国立標準技術研究所(NIST)から入手可能な科学的および数値計算のためのJavaベンチマークです。 私はこのベンチマークのソースコードをダウンロードし、プリミティブを使用した元のバージョンとラッパークラスを使用した第二のバージ 2番目のバージョンでは、ラッパークラスを使用する完全な効果を得るために、intIntegerdoubleDoubleに置き換えました。 この記事のソースコードでは、両方のバージョンを使用できます。

download

John I.Moore,Jr.

SciMarkベンチマークは、いくつかの計算ルーチンのパフォーマンスを測定し、近似Mflops(毎秒数百万の浮動小数点演算)で複合スコアを報告します。 したがって、このベンチマークには大きな数値が適しています。 表3は、私のコンピュータ上のこのベンチマークの各バージョンのいくつかの実行からの平均複合スコアを与えます。 図に示すように、SciMark2.0ベンチマークの二つのバージョンのランタイム性能は、プリミティブを持つバージョンがラッパークラスを使用するバージョンよりもほぼ五倍高速であるという点で、上記の行列乗算の結果と一致していました。

あなたは、自家製のベンチマークとより科学的なベンチマークの両方を使用して、数値計算を行うJavaプログラムのいくつかのバリエーションを見てきました。 しかし、Javaは他の言語とどのように比較されますか? 最後に、JavaのパフォーマンスがScala、C++、JavaScriptの3つの他のプログラミング言語とどのように比較されているかを簡単に見ていきます。

Scalaのベンチマーク

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

これは、Scalaの統一型システムがJavaのプリミティブ型と同等のランタイムパフォーマンスを持つことを意味しますか? どれどれどれどれどれどれどれどれどれどれどれ

私はJavaと同じくらいScalaに精通していませんが、行列乗算ベンチマークのコードをJavaからScalaに直接変換しようとしました。 その結果を以下のリスト3に示します。 私のコンピュータでscalaバージョンのベンチマークを実行したとき、平均12.30秒であり、Scalaのパフォーマンスはプリミティブを持つJavaのパフォーマンスに非常に近 その結果は私が予想していたよりもはるかに優れており、Scalaが数値型をどのように処理するかについての主張をサポートしています。

ダウンロード

John I.Moore,Jr.

リスト3。 Scalaで2つの行列を乗算する

C++のベンチマーク

C++は仮想マシンではなく”ベアメタル”上で直接実行されるため、C++はJavaよりも高速に実行されるこ さらに、Javaは配列へのアクセスをチェックして各インデックスが配列に対して宣言された境界内にあることを確認しますが、C++はそうではありま 私はC++が基本的な二次元配列を扱う上でJavaよりもやや厄介であることを発見しましたが、幸いにもこのぎこちなさの多くはクラスのプライベート部 C++では、Matrixクラスの単純なバージョンを作成し、2つの行列を乗算するための演算子*をオーバーロードしましたが、基本的な行列乗算アルゴリズムはJavaバージョンから直接変換されました。 C++のソース-コードをリスト4に示します。

ダウンロード

John I.Moore,Jr.

リスト4。 MinGW C++コンパイラでEclipse CDT(Eclipse for C++Developers)を使用すると、アプリケーションのデバッグバージョンとリリースバージョンの両方を作成できます。 C++をテストするために、リリースバージョンを数回実行し、結果を平均化しました。 予想通り、C++はこの単純なベンチマークで著しく速く実行され、私のコンピュータでは平均7.58秒でした。 生のパフォーマンスがプログラミング言語を選択するための主な要因である場合、C++は数値集約型のアプリケーションに選択される言語です。

JavaScriptのベンチマーク

さて、これは私を驚かせました。 JavaScriptが非常に動的な言語であることを考えると、私はそのパフォーマンスが最悪であり、ラッパークラスを持つJavaよりもさらに悪いと予想していました。 しかし、実際には、JavaScriptのパフォーマンスはプリミティブを持つJavaのパフォーマンスにはるかに近かった。 JavaScriptをテストするためにNodeをインストールしました。js、非常に効率的であることのための評判を持つJavaScriptエンジン。 結果は15.91秒を平均しました。 リスト5は、Nodeで実行した行列乗算ベンチマークのJavaScriptバージョンを示しています。js

ダウンロード

John I.Moore,Jr.

リスト5。 JavaScriptで2つの行列を乗算する

結論として

18年前にJavaが初めて登場したとき、数値計算に支配されているアプリケーションにとってパフォーマ しかし、時間の経過とともに、just-in-time(JIT)コンパイル(別名adaptiveまたはdynamic compilation)などの分野での技術的進歩により、これらの種類のアプリケーションに対するJavaのパフォーマ

さらに、プリミティブはガベージコレクションの必要性を排除し、オブジェクト型よりもプリミティブのパフォーマンス上の別の利点を提供します。 表4は、マイコンピュータでの行列乗算ベンチマークの実行時パフォーマンスをまとめたものです。 保守性、移植性、開発者の専門知識などの他の要因により、Javaはそのような多くのアプリケーションにとってより良い選択になります。

前述したように、Oracleは将来のバージョンのJavaでプリミティブを削除することを真剣に検討しているようです。 Javaコンパイラがプリミティブと同等のパフォーマンスのコードを生成できない限り、Javaからの削除は、特定のクラスのアプリケーション、すなわち数値計算 この記事では、この点を議論するために、行列乗算に基づく単純なベンチマークと、より科学的なベンチマークであるSciMark2.0を使用しました。

著者について

John I.Moore,Jr. シタデルの数学とコンピュータサイエンスの教授は、オブジェクト指向技術、ソフトウェア工学、応用数学の分野における特定の専門知識を持つ、産学 30年以上にわたり、彼はリレーショナルデータベースといくつかの高次言語を使用してソフトウェアを設計および開発し、バージョン1.1以降はJavaで広範囲に作業してきました。 さらに、彼は計算機科学の高度なトピックに関する多数の学術コースや産業セミナーを開発し、教えてきました。

さらに読む

  1. Paul Krillは、”Oracle lays out long-range Java intentions”(JavaWorld,March2012)で、Oracleのjavaに対する長距離計画について書いています。 この記事は、関連するコメントスレッドと一緒に、プリミティブのこの防衛を書くために私を動機としました。
  2. Szymon Guzは、”Primitives and objects benchmark in Java”(SimonOnSoftware,January2011)にプリミティブ型とラッパークラスのベンチマークにおける彼の結果について書いています。
  3. サポートwebサイトfor Programming—Principles and Practice Using C++(Addison-Wesley,2009)では、C++の作成者Bjarne Stroustrupは、この記事に付随するものよりもはるかに完全なmatrixクラスの実装を提供しています。
  4. John Rose,Brian Goetz,Guy Steeleは、”値の状態”で値型と呼ばれる概念について議論しています(OpenJDK.net、2014年4月)。 値型は、オブジェクトとプリミティブの両方のプロパティを組み合わせることにより、idなしで不変のユーザー定義集計型と考えることができます。 値型のマントラは、”クラスのようなコードで、intのように動作します。”

コメントを残す

メールアドレスが公開されることはありません。