C++とJava(とSwift)の速度比較 〜ソートアルゴリズム編〜

前回の記事(C++とSwiftの速度比較 〜ソートアルゴリズム編〜)ではC++とSwiftのソートアルゴリズムの実行速度を比較しました。今回はJavaも含めて比較するぞ!ということで取り掛かりました。

しかしいろいろあって、今回はC++とJavaのみでソートアルゴリズムの実効速度比較を行う運びとなりました。記事の最後には間接的にSwiftとJavaの比較についての考察も行っています。

概要

今回はC++とSwiftとJavaについて、比較や関数呼び出しなども含めた総合的なプログラムの実効速度を比較したいと考え、配列のソートを行うプログラムで速度の比較実験を行おうと思いました。

が、そこで大きな問題が。Javaの変数はオブジェクトへの参照値を持っています。つまりC/C++で言うポインタのようなものをデフォルトで使用しています。一方でSwiftは参照値ではない。

では比較のためにSwiftでポインタを扱おうと思ったが、これが少々面倒。そもそもSwiftでわざわざポインタを使うのもどうなの?という感じも。ではJavaで参照値ではなくオブジェクトのコピーをするように実装すればいいのでは?と思うが、やってみたらこれもなかなか面倒。

ということで、今回はC++でポインタをベースとした実装を行い、C++とJavaのみで比較を行いました。

実装した処理の流れは前回同様、

  • 標準入力からテキストデータを読み込み、オブジェクトを生成し、順次配列の後ろへ追加
  • オブジェクトのメンバを基準に配列の中身をソート

とし、その他の設定なども

  • アルゴリズムはマージソート(関数呼び出しを増やしたいのと、実装の手間削減のため、再帰関数による実装とした)
  • ソートの対象は自作のPersonクラスのオブジェクトで、id(整数型)とname(文字列型)というメンバ変数を持っており、ソートの基準はnameの昇順
  • ソート対象データは全部で50万件、nameは3〜10文字のランダムなアルファベット

としました。

実験環境

以下、今回の実験環境です。全て同じハードウェア上で動かしています。

OS

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"

C++

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
(中略)
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) 

コンパイルオプション

$ g++ -O2 -std=gnu++11

Java

$ java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

ソースコード

C++

ポインタの配列をソートするプログラムとしました。

Java

結果

標準入力からの読み込みの部分とソーティングの部分のそれぞれの実行時間を計測しました。時間の計測には、C++ではC言語のライブラリからclock_gettime関数を、JavaではThreadMXBeanを用いました。以下の時間は5回実行したときの平均を表しています。

言語 読み込み(秒) ソート(秒)
C++ 0.370 0.244
Java 0.663 0.293

JavaのソートはC++から見て20%弱遅いという結果となりました。配列アクセスの比較をした時とだいたい同じような傾向と言えるのではないでしょうか。

ここで、C++から見た遅さ前回の結果と合わせて比較し、間接的にJavaとSwiftを比較してみようと思います。あまり公正な比較ではないので、参考程度にお願いします。

言語 読み込み ソート
Java 179.14% 119.96%
Swift (-Ounchecked) 311.08% 1300.34%
Swift 527.80% 1551.49%

f:id:albow:20170902193306p:plain

JavaとSwiftそれぞれのC++からの遅さを比べていますが、かなり違いが出ました。 言語の仕様には詳しくないため何とも言えませんが、配列アクセスの比較ではSwiftがあまりに遅いということは無かったため、Swiftは関数呼び出しオブジェクトのコピー辺りが重いということでしょうか。 再帰ではなくループで実装したプログラムによる比較もしてみたいところです。

おわりに

今回はC++とJavaをソートアルゴリズムの観点から比較してみました。 Javaは思ったより速く、Swiftと比較したときほどの違いは出ませんでした。

また実装していて思ったのですが、Javaはすべて参照型となっているため、変数のコピーが大量に起こる場合でもオブジェクトのコピー頻度を意識したプログラミングをする必要が無いというところが非常に楽でした。 C++で同様のことをするためにはポインタを駆使する必要がありますからね。 一方でJavaはC++のように値とポインタを適宜使い分けるということがしにくいです。その辺りの動作を理解した上で使いこなすにはC++の方が自由度が高いため向いていると思います。

どちらにせよ、使う言語の特性をしっかりと把握した上でコーディングを行うことは必須と言えますね。


独習C++ 第4版

独習C++ 第4版


Effective Modern C++ ―C++11/14プログラムを進化させる42項目

Effective Modern C++ ―C++11/14プログラムを進化させる42項目


やさしいJava 第4版

やさしいJava 第4版