SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

JavaとC++のパフォーマンスを比較する

メモリ割り当て、ループ処理、浮動小数点演算を検証

メモリ割り当て

 C++プログラマがよく直面する議論の1つにメモリ管理問題があります。Javaはこの問題を引き受けてくれますが、C++のメモリ管理はコンストラクタとデストラクタ、および取り扱いの難しいnew/deleteペアに依存しています。

 メモリ割り当てのパフォーマンスを比較するため、まず単純なタスクとして、1,000バイトの配列を繰り返し割り当て、割り当てと割り当て解除に要する時間を測定することにします。これはJavaでは注意を要するタスクです。なぜなら、メモリを解放するための確実な方法がないからです(System.gcは「必要なら一部のメモリを解放してもいいよ」とJVMに伝える提案にすぎず、実際に解放される保証はありません)。それでも、比較の際にはこの測定値を使用するしかないので、Javaでの測定値にはメモリ解放時間が含まれない可能性があることを踏まえたうえで使用してください(結局のところ、前にも述べたとおり、C++プログラマとJavaプログラマが対立するときの主な論点の1つは、Javaではメモリ管理のことを考えなくて済むのに対し、C++ではメモリ管理に気を配る必要があるということです)。

 ここでは、整数ではなくバイトの配列を扱うことに注意してください。なぜなら、Javaの整数とC++の整数はサイズが異なるので、種類の違う配列を割り当てるのでは公正でないからです。

 そのため、Javaで割り当てを行う関数は次のようになります(IntAlloc.java)。

private void javaAlloc()
{
   System.gc();
   elements = new byte[N_ELEMS];
   elements[0] = 0;
}

 ガーベージコレクションを提案してからメモリを割り当てていることに注意してください。実際にアクセスされるまでメモリを割り当てないという「賢い」処置をコンパイラにとらせないために、配列の最初の要素を設定することで強制的にメモリ割り当てを行っています。

 このテストでは時間測定をナノ秒単位で行います。ミリ秒単位では十分でない可能性があるからです。Javaでは、これにSystem.nanoTime関数を使用します。なお、言うまでもないことでしょうが、1ミリ秒=1,000,000ナノ秒です。

 上記のコードを実行した平均時間は次のようになります。

Java memory allocation took 11,755,693

 つまり、10,000バイトを割り当てるのに約11ミリ秒を要したことになります。上記のコードでガーベージコレクションの提案を行わないと(javaAlloc関数内のSystem.gc行をコメント化すると)どうなるか試してみましょう。

Java memory allocation took 12,994

 以上の結果は、System.gc()でガーベージコレクションを提案したために、ガーベージコレクションが作動して、使用されていたメモリを再収集したことを示しています。また、ガーベージコレクションのことを気にしなければ、10,000バイトを割り当てるのに1ミリ秒もかからないことを示しています(もっと正確に言えば、約13,000ナノ秒で、1バイトあたりの平均が約1.3ナノ秒)。

 次にC++で試してみましょう(ダウンロードファイル内のIntAllocプロジェクトに入っています)。

C allocation took 3661.273463

 この結果に驚く人もいるかもしれませんが、実はC++でのメモリ割り当てはJavaのものに匹敵するのです。これらの測定値から、どちらが速いのか判断するのは難しいでしょう。いずれも1ミリ秒未満だからです。ただし、それほど大きな違いはありません。なお、繰り返しになりますが、ここに示した結果も最適化されたC++コードのものです。

 System.gcを普段使っているかどうかをJavaプログラマに尋ねてみれば(リアルタイムシステムのプログラミングをしている人は別として)、実際にSystem.gcを使っているプログラマは非常に少ないことが分かるでしょう。大多数のJavaプログラマはメモリ管理をJVMに任せているのです。というのも、JVMのメモリ管理はうまく実装されていて、自ら作動すべきタイミングを心得ているので、システムパフォーマンスにそれほど大きな影響を与えないからです。

 なお、本稿のコードをじっくり読み込み、少し変更を加えてみようと考える読者のためにあらかじめ言っておくと、割り当てをintに変更しても、JavaでもC++でもそれほどパフォーマンスが落ちることはありません。

 ここまで基本データ型に関して両方の言語の動作を見てきました。一歩進んで、オブジェクトに関してはどんな結果になるか見てみましょう。そのため、複素数データ型をマップするクラスを使って調べることにします。複素数データ型は、実数部と虚数部という2つの部分からなるデータ型で、実数部も虚数部も浮動小数点数です。このクラスを両方の言語で実装し、それを何回かインスタンス化して、何が分かるか調べてみましょう。

 Javaでは、データを格納し、それをゲッターとセッターによって公開する2つのプライベートメンバーを使用します。また、デフォルトコンストラクタの上に、2つの値を受け取って、これらのメンバーを初期化するコンストラクタを記述します(Complex.java)。

public class Complex 
{
   private double real;
   private double imaginary;
   
   public Complex()
   {
      this( 0.0, 0.0 );
   }
   
   public Complex( double real, double imaginary )
   {
      this.real = real;
      this.imaginary = imaginary;
   }
 
   public double getReal() 
   {
      return real;
   }
   public void setReal(double real) 
   {
      this.real = real;
   }
   
   
   public double getImaginary() 
   {
      return imaginary;
   }
   public void setImaginary(double imaginary) 
   {
      this.imaginary = imaginary;
   }
}

 同様に、C++では次のように実装します。

class Complex
{
    public:
        Complex(double real = 0, double imaginary = 0) 
        {
            this->real = real;
            this->imaginary = imaginary;
        }
        
        double getReal()
        {
            return real;
        }
        void setReal( double real ) 
        {
            this->real = real;
        }
        
        double getImaginary()
        {
            return imaginary;
        }
        void setImaginary( double imaginary )
        {
            this->imaginary = imaginary;
        }
        
    private:
        double real;
        double imaginary;
};

 ここで使用しているコードは非常に単純です(このコードはダウンロードファイル内のComplexCreateプロジェクトおよびComplexCreate.javaに入っています)。ただし、大きな違いがあります。Javaのインスタンス化プロセスは、次のような2つのステップになっています。

  1. 実際の配列のメモリを割り当てる
  2. 各オブジェクトを作成する

 それに対し、C++ではこれを1つのステップで行えます。

arr = new Complex[N_GENERATED];

 new演算子を使用すると、実は1つのステップでアイテムが作成されて初期化されます。C++コードとJavaコードを実行すると、次の結果が得られます。

(Java) Java create took 710788
 
(C++) C instantiation took 29348.262052

 この結果もナノ秒単位です。およそ710,000(Java)対29,000(C++)という比較です。この意味に関してはあえて触れません。

条件付きでC++の勝ち

 プログラミング言語で扱うのは、もちろんメモリ割り当て、ループ処理、浮動小数点演算だけではありませんが、これらをまったく使用しないプログラムを見つけるのは難しいでしょう。これらの処理を行うなら、どちらが好きかという話は別にして、最適化されたC++コンパイラの方がJavaよりも速いコードを生成できます。もっとも、Javaコンパイル自体に(筆者は特別なフラグなしで標準JDKコンパイラを使用しました)、本稿で述べてきたようなケースで実行速度を向上させる最適化機能がもっと必要なのかという疑問もあります。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Liviu Tudor(Liviu Tudor)

英国在住のJavaコンサルタント。特にオンラインメディアセクタの高可用性システムに関して豊富な経験を持つ。Javaに長年取り組んでいるうちに、パフォーマンスが問題になるアプリケーションをうまく機能させるために必要なのは肥大したミドルウェアフレームワークではなく「低レベル」のコアJavaであると悟る。...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4935 2010/03/08 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー