Хочу переписать свою поделку с унылого C++ на Java или даже сразу на Scala. В программе очень много операций над разными двумерными объектами, поэтому большая часть данных - это координаты на плоскости и их преобразования.
Решил проверить насколько быстро такие операции будут работать в в Java:
import java.util.ArrayList;
import java.util.List;
public class CalcVectors {
private static class Vector2D {
public final double x, y;
private Vector2D(double x, double y) {
this.x = x;
this.y = y;
}
public Vector2D add(Vector2D v) {
return new Vector2D(x + v.x, y + v.y);
}
public Vector2D mult(Transform2D t) {
return new Vector2D(x * t.cos - y * t.sin + t.tx,
y * t.sin + y * t.cos + t.ty);
}
@Override
public String toString() {
return "Vector2D(" + "x=" + x + ", y=" + y + ')';
}
}
private static class Transform2D {
public final double cos, sin, tx, ty;
private Transform2D(double angle, double tx, double ty) {
this.cos = Math.cos(angle);
this.sin = Math.sin(angle);
this.tx = tx;
this.ty = ty;
}
}
private static Vector2D calc(List<Vector2D> l, Transform2D t) {
Vector2D res = new Vector2D(0, 0);
for (Vector2D v : l) {
res = res.add(v.mult(t));
}
return res;
}
private static void run(String str, List<Vector2D> list, Transform2D transf) {
System.out.println("Start of " + str);
Vector2D vector = null;
int repeat = 1000;
long started = System.nanoTime();
for (int i = 0; i < repeat; ++i) {
vector = calc(list, transf);
}
double totalTime = (System.nanoTime() - started)*1e-9;
System.out.println("Res = " + vector);
System.out.println("Total time = " + totalTime + " (sec)");
System.out.println("Average time = " + totalTime/repeat + " (sec)");
System.out.println("End of " + str);
}
public static void main(String[] args) {
final int size = 1000000;
List<Vector2D> list = new ArrayList<Vector2D>(size);
for (int i = 0; i < size; ++i) {
double d = (1.0*i)/size;
list.add(new Vector2D(d, d));
}
Transform2D transf = new Transform2D(1.0, -2.5, 5.0);
run("Warm Up", list, transf);
System.out.println();
run("Actual", list, transf);
}
}
Запускаю вот так:
$ java -server -XX:+PrintCompilation CalcVectors
208 1 java.lang.Object::<init> (1 bytes)
210 2 java.util.ArrayList::add (29 bytes)
220 3 java.util.ArrayList::ensureCapacityInternal (26 bytes)
222 4 CalcVectors$Vector2D::<init> (7 bytes)
222 5 CalcVectors$Vector2D::<init> (15 bytes)
223 1 % CalcVectors::main @ 12 (90 bytes)
622 1 % CalcVectors::main @ -2 (90 bytes) made not entrant
Start of Warm Up
630 6 java.util.ArrayList::access$100 (5 bytes)
636 7 java.util.ArrayList$Itr::hasNext (20 bytes)
639 8 java.util.ArrayList$Itr::next (66 bytes)
642 10 java.util.ArrayList::access$200 (5 bytes)
644 9 java.util.ArrayList$Itr::checkForComodification (23 bytes)
652 11 CalcVectors$Vector2D::mult (56 bytes)
656 12 CalcVectors$Vector2D::add (26 bytes)
659 2 % CalcVectors::calc @ 18 (54 bytes)
1219 13 CalcVectors::calc (54 bytes)
Res = Vector2D(x=-2650584.18888554, y=5690885.954451363)
Total time = 32.180277053000005 (sec)
Average time = 0.03218027705300001 (sec)
End of Warm Up
Start of Actual
Res = Vector2D(x=-2650584.18888554, y=5690885.954451363)
Total time = 31.393999127 (sec)
Average time = 0.031393999127 (sec)
End of Actual
Видно, что на втором запуске JIT уже ничего докомпилировал. Получилось как-то заметно медленее, чем такой же вариант в C++:
#include <cmath>
#include <ctime>
#include <vector>
#include <iostream>
struct vector2d {
double x, y;
vector2d(double x = 0, double y = 0) : x(x), y(y) {}
};
vector2d operator+(const vector2d& v0, const vector2d& v1) {
return vector2d(v0.x + v1.x, v0.y + v1.y);
}
struct transform2d {
double cos, sin, tx, ty;
transform2d(double angle, double tx, double ty)
: cos(std::cos(angle)), sin(std::sin(angle)), tx(tx), ty(ty) {}
};
vector2d operator*(const vector2d& v, const transform2d& t) {
double x = v.x*t.cos - v.y*t.sin + t.tx;
double y = v.x*t.sin + v.y*t.cos + t.ty;
return vector2d(x, y);
}
vector2d calc(const std::vector<vector2d>& vects, const transform2d& transf) {
vector2d v;
for (size_t i = 0; i < vects.size(); ++i) {
v = v + vects[i]*transf;
}
return v;
}
int main() {
std::vector<vector2d> vects(1000000);
for (size_t i = 0; i < vects.size(); ++i) {
double x = (1.0*i)/vects.size();
vects[i] = vector2d(x, x);
}
transform2d tr(1.0, -2.5, 5.0);
const int repeat = 1000;
vector2d res;
std::clock_t start = std::clock();
for (int i = 0; i < repeat; ++i) {
res = calc(vects, tr);
}
double total = ((double)(std::clock() - start))/CLOCKS_PER_SEC;
std::cout << "Res = vector2d(" << res.x << "," << res.y << ")\n"
<< "Total time = " << total << " (sec)\n"
<< "Average time = " << total/repeat << " (sec)" << std::endl;
}
Запуск C++:
$ ./a.out
Res = vector2d(-2.65058e+06,5.69089e+06)
Total time = 8.52 (sec)
Average time = 0.00852 (sec)
Почти в 4 раза быстрее по сравнению с Java вариантом.
Кроме того, по памяти у Java также все гораздо хуже получилось: более 100Мб из кучи, в то время как C++ вариант 16Мб с копейками, что и ожидалось, т.к. один миллион структур vector2d как раз 16Мб и занимает.
Наверняка что-то сделал не так в Java варианте (ведь Java должна быть быстра почти как C++). Подскажите, как нужно правильно переписать Java вариант, чтобы приблизиться к C++?