ふと、C/C++で作成したプログラムをJavaに移植したいと思ったのですが、C/C++のプログラムとは天体の位置計算をするプログラムで三角関数の塊、ほぼCPUの能力で処理するようなもので、はてさて、この種の所謂科学技術計算ってJavaが使い物になるのか、と疑問を持ちました。
JavaはC/C++よりも遅い。これは自明です。なぜならC/C++のプログラムはCPUネイティブ、即ちコンパイルによりCPUが直接実行できる所謂「機械語」のコードが生成されますが、javaはコンパイルにより「VM」すなわりJava仮想マシン用のコードを生成し、CPU上でまずJava VMが動作し、このJava VM上でjavaのプログラムが実行されるのですから、C/C++と較べてワンクッション多い分、そのオーバヘッドが処理速度に加わります。
問題はそのオーバヘッドが小さいのか大きいかで、これは処理するプログラムの処理内容に大きく依存します。
ネットワークアクセス、DBアクセスが多いプログラムは相対的にCPU処理の割合が少なくなり、たとえCPU処理が大きく遅くなっても全体の処理時間への影響は許容可能かもしれませんが、三角関数の塊の処理ではCPU処理時間がほぼ全てです。
そういうプログラムはC/C++でしょう、と思っているのが単なる食わず嫌いなのか、確認してみることにしました。
実験内容
double x,y,zをメンバーに持つ方向余弦クラスを与えたオイラー角でZ-X-Z回転させる、これを1000万回実行して処理時間を図ります。
結果
結果を先に書きます。
Javaプログラムの処理時間は、783ミリ秒でした。
一方、C++プログラムの処理時間は、90ミリ秒でした。
C++の方が早いはずとは思っていましたが想定外の結果、レベルが違う大差でした。
Java プログラム
Direction.java は方向余弦と呼ばれる空間上の方向を示すための3つ実数を有するクラスです。しかし幾何学な意味はここでは関係ありません。
Test.javaはメインルーチンで、Directionクラスに格納されたこれら3つ実数をrotateX/rotateZメソッドなどで三角関数を使用した変換を1000万回連続実行して、処理時刻をシステム時計で計測します。
- Direction.java
package testtest; public class Direction { public double x = 0; public double y = 0; public double z = 0; public Direction(double x , double y , double z) { this.x = x; this.y = y; this.z = z; } public Direction rotateX(double a) { double COSA = Math.cos(a); double SINA = Math.sin(a); double xx = x; double yy = y * COSA + z * SINA; double zz = y * -SINA + z * COSA; x = xx; y = yy; z = zz; return this; } public Direction rotateY(double a) { double COSA = Math.cos(a); double SINA = Math.sin(a); double xx = x * COSA + z * SINA; double yy = y; double zz = x * -SINA + z * COSA; x = xx; y = yy; z = zz; return this; } public Direction rotateZ(double a) { double COSA = Math.cos(a); double SINA = Math.sin(a); double xx = x * COSA + y * SINA; double yy = x * -SINA + y * COSA; double zz = z; x = xx; y = yy; z = zz; return this; } }
- Test.java
package testtest; public class Test { public static void main(String[] args) { System.out.println("Start"); long t0 = System.currentTimeMillis(); Direction dire = new Direction(1, 0, 0); for(int i=0; i<10000000; i++) { dire.rotateZ(1.2).rotateX(1.1).rotateZ(-0.9); } long t = System.currentTimeMillis() - t0; System.out.printf("Direction=(%f, %f, %f)\n", dire.x, dire.y, dire.z); System.out.printf("End %d ms\n", t); } }
実行結果
C:\workspace_TCP\seiichis\bin>java testtest.Test Start Direction=(-0.016389, 0.764198, -0.644774) End 783 ms
C++ プログラム
Direction.h
class Direction { public: double x = 0; double y = 0; double z = 0; public: Direction(double x = 0, double y = 0, double z = 0) { this->x = x; this->y = y; this->z = z; } Direction& rotateX(double a); Direction& rotateY(double a); Direction& rotateZ(double a); };
Direction.cpp
#include <math.h>
#include "Direction.h" Direction& Direction::rotateX(double a) { double COSA = cos(a); double SINA = sin(a); double xx = x; double yy = y * COSA + z * SINA; double zz = y * -SINA + z * COSA; x = xx; y = yy; z = zz; return *this; } Direction& Direction::rotateY(double a) { double COSA = cos(a); double SINA = sin(a); double xx = x * COSA + z * SINA; double yy = y; double zz = x * -SINA + z * COSA; x = xx; y = yy; z = zz; return *this; } Direction& Direction::rotateZ(double a) { double COSA = cos(a); double SINA = sin(a); double xx = x * COSA + y * SINA; double yy = x * -SINA + y * COSA; double zz = z; x = xx; y = yy; z = zz; return *this; }
Test.cpp
#include <stdio.h>#include "Direction.h" int main(int argc, _char* argv[]) { printf("Start\n"); LONGLONG t0 = Get64Time(); Direction dire(1, 0, 0); for (int i = 0; i < 10000000; i++) { dire.rotateZ(1.2).rotateX(1.1).rotateZ(-0.9); } LONGLONG t = Get64Time() - t0; printf("Direction=(%lf, %lf, %lf)\n", dire.x, dire.y, dire.z); printf("End %d ms\n", (int)t); }
実行結果
速度もさることながら、三角関数1000万回実行でJavaとC++のプログラムの計算結果が同じになるのかも見ものでしたが、見事同じ結果となりました。
C:\Data\project2\AstroNavi2\x64\Release>JpldeTest.exe Start Direction=(-0.016389, 0.764198, -0.644774) End 90 ms