JavaとC++の比較 (ジャバとシープラスプラスのひかく)の記事では、Java とC++ の比較について説明する。
設計思想
C++とJavaとの違いは、それら言語の歴史から辿ることができる。
C++とJavaは開発の目的が異なるため、両者の方針 とトレードオフ に違いが生じている。
C++
Java
ネイティブコード
マネージコード
Cの (部分的な) 上位互換
C/C++との互換性はない
プログラマを信頼する
プログラマを守る
ハードウェアに近い低レベル(下位レベル)機能を操作できる
メモリを抽象化した型やオブジェクトを通してのみアクセス可能
簡潔な表現
明確な表現
明示的な型破壊を許可
型安全性
マルチパラダイム (手続き型 、オブジェクト指向 、総称型 、関数型 )
オブジェクト指向、総称型、関数型
演算子 多重定義
演算子の効果は不変
限られた範囲の標準ライブラリ
(GUI、ネットワーク、マルチスレッドを含む)機能豊富で容易に使用できる標準ライブラリ
組み込み、パーソナルコンピュータ、ワークステーション
業務システム(Webフロントエンド、Webバックエンド)、携帯端末
C言語とC++は相互運用性が確保されており、C++からCのライブラリ(関数 群)を直接利用することが可能になっている。なお、2023年現在のC/C++最新規格はそれぞれC17 (英語版 ) /C18 (英語版 ) およびC++20 だが、双方に独自の機能追加と仕様変更が重ねられており、C言語に対する完全な上位互換性はなくなっている。ただし、互いの差異を埋めて互換性を向上するための機能追加もなされている。
また、JavaはC/C++との互換性はないが、Scala やKotlin 、Groovy といった後発のJava仮想マシン (JVM) ベースのABI 互換言語との間で相互運用が可能になっている。
言語の特徴
文法
Java文法 はシンプルなLALRパーサ によって解析できる文脈自由文法 である。C++の構文解析は、それよりも複雑である。例えば、Foo<1>(3);
は、Fooが変数であれば比較シーケンスであるが、Fooがクラス のテンプレート名であればオブジェクト を生成する。
C++では名前空間 レベルの定数、変数 、関数 が認められている。Javaでは、宣言はクラス やインタフェース の中に書かなければならない。
C++のconst
は、「論理的に読み取り専用データである」ことを示す。Javaのfinal
は、「変数が再び割り当てられない」ことを示す。const int
とfinal int
など、基本型にとってはこれらは概ね等価であるが、C++では変数および定数の宣言時初期化もしくは定義時初期化が必要であるのに対し、Javaでは初期化が一回だけ実行されるという条件が満たされればよい。また、C++のポインタ自身に対するconst
修飾は、Javaの参照に対するfinal
修飾と同じ意味を持つ。
C++11 では、コンパイル時に値が決まる定数式 (constant expression) すなわちリテラル を表すconstexpr
修飾子をサポートするようになった[ 1] 。Javaでは通例static final
フィールド で定数を定義する。
C++
Java
const int n = 100 ; // 値型。
n = 0 ; // 誤り。
struct Rectangle { int x , y , width , height ; };
Rectangle * const p = new Rectangle (); // ポインタ自身を変更不能とする。
p = new Rectangle (); // 誤り。
p -> x = 5 ; // 正しい。p は依然として同じ Rectangle を参照している。
delete p ;
const Rectangle * p = new Rectangle (); // ポインタの参照先を変更不能とする。
delete p ;
p = new Rectangle (); // 正しい。
p -> x = 5 ; // 誤り。
const Rectangle & r = * p ;
r = Rectangle (); // 誤り。
r . x = 5 ; // 誤り。
delete p ;
const Rectangle r = Rectangle (); // 値型。
r = Rectangle (); // 誤り。
r . x = 5 ; // 誤り。r は Rectangle 型の定数。
final int n = 100 ; // プリミティブ型。
n = 0 ; // 誤り。
class Rectangle { int x , y , width , height ; };
final Rectangle r = new Rectangle (); // 参照型。
r = new Rectangle (); // 誤り。
r . x = 5 ; // 正しい。r は依然として同じ Rectangle を参照している。
final int n ;
final Rectangle r ;
n = 100 ; // 正しい。
r = new Rectangle (); // 正しい。
C++においてconst
修飾されたメンバ関数は、関数内でメンバ変数を変更することができなくなる(mutable
指定された変数を除く)。Javaには相当機能はない。
Javaにおいてfinal修飾されたメソッドは派生クラスでオーバーライド できなくなる。また、final修飾されたクラスは派生クラスを定義できなくなる。なお、C++11 ではJavaのようにメンバ関数のオーバーライドや派生クラスの定義を禁止するfinal修飾子が追加された。
C++はgoto文 をサポートする。Javaはサポートしないが、ラベル付break文 とラベル付continue文 で、構造上ややgoto
ライクな機能を提供する。実際には、Javaは、コードを読みやすくするため、構造化制御フロー を強要する。
C++はJavaが持たないやや低レベルな機能を提供する。C++には、特有のメモリ記憶位置や低レベルオペレーティングシステム コンポーネントを書くために必要なタスクを操るのに役立つポインタ がある。同様にして、多くのC++コンパイラはインラインアセンブラ をサポートする。Javaでは、そのようなコードは全て外部ライブラリに配置し、Java Native Interface (JNI) を通してアクセスしなければならない。そのため、呼び出しのたびに、大きなオーバーヘッドが発生する。
意味論
C++はC との互換性を保つため組込型の暗黙な型変換 をある程度許可しているが、大抵のコンパイラでは警告を出す。また、複合型 の暗黙型変換も定義できる。一方、Javaではプリミティブ型の暗黙的な変換として数値型の精度 が大きくなる型変換(拡張変換 )のみを許可する。数値型の精度が低下する暗黙的な型変換(縮小変換 )は許可されず、明示的なキャスト を要求する。
この影響はJavaとC++双方にある条件式(if
、while
とfor
の脱出条件)にも現れる。C++では条件式の型は厳密にbool
である必要はなく、文脈的に変換可能であればよい[ 2] 。Cとの互換性から、C++のbool
は整数型 の一種であり、他の整数型や浮動小数点数 型、ポインタなどから暗黙変換することができ、ゼロあるいはゼロに相当する値の場合はfalse
に、それ以外はtrue
に変換され、またこの条件式の文脈における暗黙変換はコンパイラ警告の対象にはならない。一方、Javaでは条件式の型はboolean
である必要があるが、boolean
は整数型ではなく、数値型からの暗黙的な型変換ができない。単純なキャスト構文による型変換もできず、boolean
に変換するためには何らかの比較演算子による明示的な比較が必要となる[ 3] 。そのため、数値型の変数a
に対してif (a = 5)
のようなコードは、Javaではコンパイルエラーになる。これは、if (a == 5)
のミスタイプ に対する対策となるが、C/C++で変数x
が非ゼロまたはnon-nullであることをチェックするif (x)
のようなコードをJavaへ移植する際には、0
またはnull
との明示的な比較が必要になる。
関数の引数を渡すとき、C++は参照渡し と値渡し 両方をサポートする。Javaではすべての引数は値渡しであるが、オブジェクト(非プリミティブ変数 )の引数は参照 になり、これは間接参照が言語に備わっていることを意味する。
Javaのプリミティブ型は大きさと値の範囲が指定されている。一方、C++の組み込み型は最小限度が定められているものの、正確な大きさは定められておらず、環境によって異なる。また同じコンパイラでもバージョンが違えば型の大きさが異なることもあるし、コンパイラの設定で変更可能な場合もある。C++で値の範囲が指定されていないとは、仮に同じ16ビットの符号付き整数だとしても、ある環境では2の補数 で[-32768, 32767]、別の環境では1の補数 で[-32767, 32767]の範囲になるという例があるということである。C++11 ではサイズと内部表現が規定された整数型(std::int32_t
など)がオプションとして追加された。C++20 では符号付き整数が2の補数であることが規定された[ 4] 。
C++の浮動小数点数の丸め誤差と精度と演算はプラットフォームに依存する。Javaは異なるプラットフォームでの一貫した結果を保証する高精度浮動小数点モデル を提供しているが、通常は最適な浮動小数点演算性能を得るためにより大雑把な演算モードが使われる。
C++ではポインタ を使ってメモリアドレスを直接操作できる。Javaはメモリアドレスを直接操作できるポインタを持っていない(オブジェクトの参照と配列参照だけはポインタを持っているが、どちらもメモリアドレスの直接アクセスを許可しない)。C++ではポインタへのポインタを定義できるが、Javaではオブジェクトアクセスにだけ参照を用いる。
どちらの言語も配列は固定の長さをもつ。C++の配列はメモリ空間上のオブジェクトの連続であり、配列自身をオブジェクトのように扱うことはできない(第一級オブジェクト ではない)。通例、C言語のように配列先頭要素へのポインタを用いて配列の受け渡しを行うが、C++ではサイズの指定された固定長配列への参照を定義することもできる。配列範囲外アクセスはチェックされない。なお、構造体 あるいはクラスでラップするか、C++11 で追加された標準ライブラリに含まれるstd::array
を用いることで、固定長配列を第一級オブジェクトとして扱うことができる。Javaの配列は第一級オブジェクトであるが、配列の一部分のみを参照するサブ配列は定義できず、インデックス範囲を別途指定する必要がある。また、配列範囲外アクセスのチェックが強制される。
C++では関数へのポインタ (あるいは関数への参照)によって、関数あるいはメンバ関数を間接参照することができる。また、ポインタによって関数オブジェクト を指す方法もある。Java 7以前では代替方法としてインタフェースを利用するしかなかったが、Java 8ではメソッド参照の機能が追加された。
C++ではプログラマが演算子を多重定義 できる(利用者定義演算子 )。Javaは演算子多重定義をサポートしない。文字列型java.lang.String
に対してのみ、文字列連結 が可能な演算子+
と+=
が予め用意されているだけである。
Javaはリフレクション や任意に新しいコードを動的ロード する機能をサポートする標準API を持つ。
Javaは1.5以降でジェネリクス をサポートする。ただし型引数に使えるのは参照型のみであり、int
のようなプリミティブ型は使用できないため、java.lang.Integer
のようなプリミティブラッパークラス でボックス化 する必要がある。C++はテンプレート によりジェネリックプログラミングをサポートする。名前解決が失敗しない限り、型引数にはいかなる型でも使えるため、静的ダックタイピング に応用できる強力な柔軟性を持つが、適合しなかった場合のコンパイルエラーメッセージが膨大・難解になる欠点がある。この点に関してはC++20 の「コンセプト」機能によってある程度の解消が図られている[ 6] 。
JavaとC++はいずれも、ネイティブ型(これらも"基本型"または"組込型"として知られる)とユーザー定義型(これも"複合型"として知られる)を区別する。Javaでは、ネイティブ型は値としての意味しかなく、複合型は参照としての意味だけを持つ。C++では、すべての型が値としての意味を持つが、任意の型の参照を作ることが可能である。
C++は任意のクラスの多重継承 をサポートする。また、状態の多重継承にまつわる問題を回避するための機構として、仮想継承 をサポートする。Javaは型の多重継承をインタフェース によりサポートするが、実装は単一継承のみをサポートする。Javaでは、サブクラスはただ1つのスーパークラスからのみ派生することができるが、クラスは複数のインタフェースを実装することができる。なお、Java 8ではインタフェースのデフォルトメソッドにより、実装の多重継承をサポートするようになったが、状態の多重継承は依然としてサポートしない。
Javaはインタフェースとクラスを明確に区別している。C++では、純粋仮想関数(抽象メソッドにあたる)を並べたクラスでインタフェースを表現し、複数のインタフェースの実装は多重継承によって模倣される。
Javaはマルチスレッド をサポートした標準ライブラリと言語機能を持つ。Javaのキーワード synchronized
はマルチスレッドアプリケーションをサポートするシンプルでセキュアな相互排他ロック(mutex) を提供するが、synchronized セクションはLIFO オーダーで残されなければならない。Java 1.5では並行プログラミング のためのライブラリが強化され、java.util.concurrent.Semaphore
クラスをはじめとする、柔軟なMutexロックメカニズムを提供するための明示的な同期オブジェクトなどもサポートするようになった。一方、C++では長らくスレッドが標準化されていなかったため、プラットフォームごとのスレッド機能を使うか、Boost C++ライブラリ などを利用する必要があったが、C++11 でスレッドが標準化された。
リソース管理
Javaは自動メモリ管理のためのガベージコレクション (GC) を必要とする。GCの具体的なアルゴリズムや実装形態はJVMに左右されるが、マーク・アンド・スイープ をベースに通例世代別ガベージコレクション が採用されている[ 7] [ 8] 。このため、循環参照 によるメモリリークは原理的に発生しないが、オブジェクトが実際に破棄されるタイミングは非決定論的である。C++のメモリ管理は普通、手動で行われるか、ライブラリによって実装されたスマートポインタ を通して行われる。C++でガベージコレクションを実装することも不可能ではないが、標準規格で要求されているわけではなく、実際には滅多に使われない。また、C++ではオブジェクトを再配置できないが、一般にオブジェクトの再配置が可能であれば、明示に解放するスタイルより空間と時間効率が良くなることが知られている。
C++のスマートポインタのうち、複数個所でインスタンスが共有されるもの(共有ポインタ)に関しては主に参照カウント が用いられる。その場合、循環参照の注意が必要という欠点がある。Boost C++ライブラリ のboost::shared_ptr
/boost::scoped_ptr
およびC++11 で追加された標準ライブラリのstd::shared_ptr
/std::unique_ptr
を筆頭に、様々なサードパーティ製のライブラリでもスマートポインタが実装されている。
メモリ割り当ては、C++では任意に可能だが、Javaはオブジェクトインスタンス化を通してのみ可能である。Javaでは、バイト配列を作ることで任意のブロック割り当てを再現できる。もっとも、Javaの配列 もオブジェクトである。
JavaとC++はリソース管理に異なる手法を用いる。Javaは主にガベージコレクションに頼り、メモリの再利用だけができ、他のリソースは最後まで回収されないかもしれない。だが、C++は主にRAII (Resource Acquisition Is Initialization) というイディオムに頼る。これは2つの言語間の以下のような様々な違いに現れている。
C++では、自動変数(ローカル変数 )は構造体やクラスのような複合型であっても組込型同様スタックに割り当てられ、変数の属するブロック を抜けるときに破棄される。巨大な固定長配列を内包する複合型を定義することもできるが、実行時にスタックオーバーフロー を引き起こす可能性があるため、通例malloc 関数やnew演算子 などによる動的メモリ確保 を使ってヒープに割り当てる。Javaでは、ローカル変数はスタックに割り当てられるものの、クラスや配列といった複合型(参照型)のオブジェクト本体は常にヒープ に割り当てられ、ガベージコレクタによって回収される(これはあくまで概念上の話で、実装上はエスケープ解析 最適化でスタックにオブジェクトを割り当てる仮想マシンもある[ 9] )。
C++はデストラクタ を持っているが、Javaはファイナライザ を持っている。双方はオブジェクトの解放時に優先的に呼び出されるが、それらは重大性が異なる。
C++オブジェクトのデストラクタは、オブジェクトが解放されるときに必ず呼び出される(ようにコンパイラは実装しなければならない)。例外が投げられたときでも、スタック上のオブジェクトは、スタックの巻き戻し(アンワインド)に伴ってデストラクタが呼ばれる。また、デストラクタは定義さえすれば、利用する側に特別な記述は不要である。このことを利用して確実なリソースの解放を行うのが広義のRAIIである。
Javaでは、オブジェクト解放はガベージコレクタによって暗黙のうちに実行される。Javaオブジェクトのファイナライザは、最後にアクセスされた後と、実際に解放される前に、ときどき非同期 に呼び出されるが、決して何も起こらないかも知れない。ファイナライザを要求するオブジェクトはごくわずかにすぎない。ファイナライザは解放状態を優先するオブジェクトの多少のクリーンナップ を保証しなければならないオブジェクトによって要求されるだけであり、だいたいはJVM外のリソースへ放出される。Javaでは安全な同期によるリソース解放は、try/finally文を構築して明示的に行われなければならない。ファイナライザの動作タイミングはGCに左右され、非決定論的であり、また誤った使い方をするとトラブルの原因になるため、Java 9以降はファイナライザの使用が非推奨となっている[ 10] 。
C++では、時としてdangling pointer (破棄されたオブジェクトを参照するポインタ)が存在してしまう。dangling pointerを間接参照するときは、基本的にプログラムのバグである。Javaでは、ガベージコレクタは参照されているオブジェクトは解放しない。
C++では初期化されていないプリミティブ なオブジェクトを持つことが可能である。Javaでは、初期化が強制される。
C++では、領域を割り当てられたが到達不能であるオブジェクトが発生してしまうことがある。到達不能オブジェクト とは、それへの到達可能な参照が全く存在しないオブジェクトのことである。到達不能オブジェクトは解放(破棄)することができず、メモリリーク を引き起こす。それとは対照的に、Javaではオブジェクトは、それがユーザープログラムによって到達不可能になる「までに」ガベージコレクタによって解放される (注: 異なる到達可能性の「強さ」を考慮に入れた、Javaのガベージコレクタとともに働く、「弱い参照 」がサポートされている)。
Javaでは非メモリリソースをリークしないように注意深く解放コードを逐一書く必要がある(ただしJava 7のtry-with-resources文により、ある程度緩和されている)。一方でC++では、前述のRAIIによってリークしにくい例外安全なコードを書きやすくなっている。
ライブラリ
JavaはC++と比べ標準ライブラリ の提供する範囲が広い。標準C++ライブラリ は文字列 、コンテナ 、IOストリーム のような比較的一般的な目的のコンポーネントだけを提供する。Java標準ライブラリ はネットワーキング 、グラフィカルユーザインタフェース 、XML 処理、ロギング 、データベース アクセス、暗号化 やそのほか様々な領域のコンポーネントを含む。このような機能は、C++ではサードパーティー 製のライブラリやOS 固有のAPIによって実現されていることが多いが、どんな環境でも用意されているとは限らない。
C++はCに対する部分的な上位互換性を持つため、(多くのオペレーティングシステム のAPI のような)Cライブラリも直接使用できる。Javaでは、そのような環境固有のライブラリで提供される機能の多くが、クロスプラットフォーム でリッチな標準ライブラリで提供される。その一方で、Javaからネイティブなオペレーティングシステムやハードウェア機能に直接アクセスするには、Java Native Interface (JNI) を使用する必要がある。
拡張命令のサポート
x86 のSSE やAVX、ARM のNEONのようなCPU固有のSIMD 拡張命令を使ってベクトル化する場合、C++ではコンパイラがサポートする組み込み関数(intrinsics)を使ったり、OpenMP ディレクティブによるベクトル化ヒントを挿入したり、コンパイラの最適化に任せたり、といった方法がある。ただしコンパイル時の静的なコード生成の場合、実行環境でサポートされていない命令に到達するとアプリケーションがクラッシュしてしまうため、実行環境に応じて動的分岐するディスパッチ処理が必要となる。また、フォールバック処理の数に応じてバイナリも肥大化してしまう。
JavaではそのようなCPU固有の拡張命令や最適化ヒントを直接ソースコード中に記述することはできず、JNIを利用するしかないが、JITコンパイルによって実行環境に合わせた最適化も可能であるため、VMが対応している命令セットであれば実行環境がサポートする最上位の高速な命令セットを使用して最適化を図ることもできる[ 11] [ 12] 。JIT最適化を利用する場合は実行環境に応じたバイナリを事前に用意する必要はない。ただし最適化の度合いはJITコンパイラの性能に左右される。
ランタイム
C++は通常、機械語 に直接コンパイルされてから、オペレーティングシステム およびCPUによって直接実行される。Javaは通常バイトコード にコンパイルされてからJava仮想マシン (JVM) がインタプリタ でバイトコードを解釈するか、またはJIT がバイトコードをマシンコードにコンパイルしつつ実行される。理論上、動的再コンパイル はどの言語でも使うことができる(しかしJavaのほうが向いている)が、現在のところ、どちらの言語でも動的に再コンパイルされることは稀である。
強制によらない表現力のため、C++の多くのエラー要因(範囲外チェックされない配列アクセス、未使用ポインタ、型の不一致など)はコンパイル時または実行時の不適当なオーバーヘッド無しに信頼できるチェックを行えない。このため、低レベルバッファオーバフロー 、ページフォールト 、セグメンテーションフォルト を導いてしまう。標準やサードパーティーのライブラリがそのようなエラーを避けることを助ける高水準な(動的配列 、リスト 、マップ のような)抽象概念を提供している。一方、Javaではそのようなエラーは単純に起こすことも、JVM に検出されることも無く、例外 によってアプリケーションに報告される。
Javaは、配列アクセスの境界チェック を行い、そして領域外にアクセスすることが判明したときに明確な振る舞い(例外の送出)を要求する。これにより、一般に実行が低速になる代わりに不安定さの源が除去される。ただし、コンパイラ解析 で不必要な境界チェックが消去される場合もある。C++はネイティブな配列の配列外アクセスの振る舞いを要求しないため、通常は境界チェックしないのが一般的である。ただしstd::vector
のようなC++標準ライブラリでは、at()
メンバ関数の使用という形で、境界チェック付きアクセスを任意に選択できる。要約すると、Javaの配列は「常に安全で、厳しく強いられる、可能な限り高速」だがC++のネイティブ配列は「常に高速、完全に強制されない、潜在的に危険」ということである。
その他
JavaとC++では多くのソースファイルでコードを分割するために異なる方法を使用する。Javaはすべてのプログラム定義でファイル名とパスが影響するパッケージ システムを使用する。Javaでは、コンパイラは実行可能クラスファイル をインポートする。C++はソースファイル間で宣言を分割するヘッダファイル のソースコード 包含システムおよび名前空間 を使用する。(参考: importとincludeの比較 )
コンパイルされたJavaバイトコード ファイルは通常、C++コンパイラの出力する機械語のコードファイルよりも小さい。第一に、Javaバイトコード は通常、ネイティブな機械語 よりもコンパクトである。第二に、C++のテンプレートやマクロが類似コードの重複を発生させやすいことが挙げられる。第三に、Javaは常に標準ライブラリを動的リンク するため、標準ライブラリのコードを出力に含まないということが挙げられる。反面、Javaバイトコードを翻訳実行する環境(JVMやJIT)が要求される。
C++コンパイラは、Javaにはない、言葉通りのプリプロセッシング (前処理)の段階があることが特徴的である。これを用いるために、Javaユーザーの中には、ビルドプロセスにプリプロセッサを付加する者がいる。
双方の言語は、配列 が固定サイズである。Javaでは、配列は第一級オブジェクトであるが、C++では配列はベースとなるオブジェクトの連続した領域であり、最初の要素と随意的な配列の長さをポインタを使って参照しているに過ぎない。Javaでは、配列は境界チェックされ、長さもわかっているが、C++では配列を連続した領域として扱うだけである。C++とJava双方は、リサイズ、サイズ保存できるコンテナクラス (それぞれ、std::vector
とjava.util.Vector
またはjava.util.ArrayList
) を提供している。
Javaの除算と剰余 演算子は0を切り捨てるよう正しく定義されている。C++は、これらの演算子が0を切り捨てるか「マイナス無限大に切り捨てる」かを明確に指定しない。Javaでは、-3 / 2
は常に-1
となる。一方、C++ではプラットフォームに依存し、-1を返すかも知れないし-2を返すかも知れない。C99 およびC++11 はJavaと同じように除算の仕様を定義しており、すべてのaとb (b != 0) で(a/b)*b + (a%b) == a
を保証する。古いC/C++規格にこの保証がない理由は、仮にCPUの除算命令がこのような定義でなかったとしても、C/C++の除算を直接CPUの除算命令にコンパイルできるようにするためである。ただし、古いC/C++でも標準ライブラリのdiv()
関数 (<stdlib.h>/<cstdlib>) を用いれば常に-3 / 2
の商として-1
という結果を得られる。
パフォーマンス
このセクションでは、Microsoft Windows やLinux のような一般的なOSでのC++とJava相互の演算パフォーマンスを比較する。
Javaの初期バージョンは、C++のような静的コンパイルされる言語に比べ著しく性能が低かった。これは、C++ではソースコードはハードウェアが直接に解釈できる機械語にコンパイルされるのに対し、Javaでは共通の(ハードウェアに依存しない)仮想機械語であるJavaバイトコードにコンパイルされ、それをJava仮想マシンがインタプリタ的に実行していたからである。例として、
Java/C++構文
C++コンパイラが生成したx86機械語コード
Javaコンパイラが生成したバイトコード
vector[i]++;
mov edx,[ebp+4h]
mov eax,[ebp+1Ch]
inc dword ptr [edx+eax*4]
aload_1
iload_2
dup2
iaload
iconst_1
iadd
iastore
のように、ソースコードレベルでは同様な命令でも、Javaバイトコードの方がネイティブな機械語よりも長くなる傾向にある (C++によるコードでは、3行目でロード、インクリメント、保存が1命令で行われており、短く済んでいる)。
しかし、Javaは長期稼働するサーバやデスクトップのためにジャストインタイム (JIT) コンパイラテクノロジを発展させ、それがC++との性能差を縮めるであろうと言われている。JITコンパイルとは、Javaバイトコードをインタプリタ的に実行するのではなく、実行時にネイティブな機械語にコンパイルしてから実行する方式である。
以下は、JavaはC++よりも高速であるという研究[ 13] の主張の一部である。
CおよびC++では、「なんでも指せる」というポインタの性質が最適化を難しくしている(ただしこの問題はC99 のrestrictキーワードによって回避できるケースもある)。
Javaでは新たに確保されたメモリがガベージコレクション によって物理的に連続した領域に集められるので、アクセスする際にキャッシュ ミスが起こりにくい。
実行時コンパイルはそれがどのプロセッサの上で実行されるか、どのコードを実行するかが解っているため、各CPUに特化したコードを生成したり、高い分岐予測 精度を実現したりできる(ホットスポット )。
一般的に、Javaは、メモリ確保やファイルI/Oのような演算においてはC++より性能がよいが、算術演算や三角関数計算ではC++の方が優れた徴候を示す[ 14] 。数値演算について述べると、Javaは新しいバージョン[どれ? ] で大きく進歩しているものの、浮動小数点数を様々なプラットフォームで再現するためのオーバーヘッド等により、未だに [いつ? ] C++やFortranより遅い[ 15] 。
脚注
注釈
^ C++17 までは、これらの型のリテラルのエンコードは実装定義であり、UTF-16/UTF-32であることがすべての処理系で保証されていなかったが、C++20 では改めてUTF-16/UTF-32に規定された[ 5] 。
出典
^ constexpr - cpprefjp C++日本語リファレンス
^ if 文 - cppreference.com
^ Chapter 4. Types, Values, and Variables | Java SE 8 Specifications > Java Language Specification | Oracle
^ 符号付き整数型が2の補数表現であることを規定 - cpprefjp C++日本語リファレンス
^ char16_tとchar32_tの文字・文字列リテラルを、文字コードUTF-16/32に規定 - cpprefjp C++日本語リファレンス
^ コンセプト - cpprefjp C++日本語リファレンス
^ Java Garbage Collection Basics - Describing Garbage Collection | Oracle
^ ガベージ・コレクタの実装 (Java SE 11) | Oracle Help Center
^ 「メモリーを意識してみよう」第4回 進化するメモリー管理 | 日経クロステック(xTECH)
^ Object (Java SE 9 & JDK 9 )
^ javaコマンド | Java SE 17 | Oracle
^ JavaでCPUを使い倒す! ~Java 9 以降の CPU 最適化を覗いてみる~(NTTデータ テクノロジーカンファレンス 2019 講演資料、2019/09/05) | PPT
^ "Performance of Java versus C++" by J.P. Lewis and Ulrich Neuman, USC, Jan. 2003 (updated 2004)
^ "Microbenchmarking C++, C# and Java" by Thomas Bruckschlegel, Dr. Dobbs, June 17, 2005
^ "Java and Numerical Computing" by Ronald F. Boisvert, José Moreira, Michael Philippsen and Roldan Pozo, NIST, Dec 2000
外部リンク