LINUX.ORG.RU

Переписать код на Java, чтобы он не тормозил и не жрал память

 ,


1

3

Хочу переписать свою поделку с унылого 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++?

★★★

Ответ на: комментарий от bhfq
$ \time -v ./1
elapsedTimeMS   = 85
cpuTimeMS       = 80
	Command being timed: "./1"
	User time (seconds): 0.08
	System time (seconds): 0.00
	Percent of CPU this job got: 89%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.08
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 69280
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 0
	Minor (reclaiming a frame) page faults: 4474
	Voluntary context switches: 11
	Involuntary context switches: 2
	Swaps: 0
	File system inputs: 0
	File system outputs: 0
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 0

это только warm up.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

естественно выделений памяти нет, т.к. для задачи не требуется.

qnikst ★★★★★
()

«Втупую» портировал на Скалу

Start of Warm Up
Res = Vector2D(x=0.0, y=0.0)
Total time = 9.189132786 (sec)
Average time = 0.009189132786 (sec)
End of Warm Up

Start of Actual
Res = Vector2D(x=0.0, y=0.0)
Total time = 8.900299112 (sec)
Average time = 0.008900299112 (sec)
End of Actual

http://pastebin.com/wjw4juVg

вектора немутабельные, но по крайней мере по скорости немутабельные даже быстрее

(scala 2.11 nightly, java 8, x64)

stevejobs ★★★★☆
()
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от stevejobs

А плюсы сколько на этом же компе показывают?

И у тебя же там boxing/unboxing, т.к. ArrayBuffer не умеет хранить примитивные типы.

У меня такой вариант на Scala совсем чуть-чуть хуже Java:

case class Vector2D(var x: Double = 0, var y: Double = 0) {
  def +=(v: Vector2D) = { x += v.x; y += v.y; this }

  def *=(t: Transform2D) = {
    val x1 = x * t.cos - y * t.sin + t.tx
    val y1 = x * t.sin + y * t.cos + t.ty
    x = x1; y = y1; this
  }
}

case class Transform2D(var cos: Double, var sin: Double,
                       var tx: Double, var ty: Double)

object Transform2D {
  def apply(angle: Double, shift: Vector2D) =
    new Transform2D(math.cos(angle), math.sin(angle), shift.x, shift.y)
}

object CalcVectors extends App {
  def calc(vects: VectorOfVector2D, t: Transform2D) = {
    var res = Vector2D()
    var i = 0
    while (i < vects.length) {
      res += (vects(i) *= t)
      i += 1
    }
    res
  }

  def run(s: String, vects: VectorOfVector2D, transf: Transform2D) {
    println(s"Start of $s")
    val repeat = 1000
    val start = System.nanoTime()
    var res = Vector2D()
    var i = 0
    while (i < repeat) {
      i += 1
      res = calc(vects, transf)
    }
    val totalTime = (System.nanoTime() - start) * 1e-9
    println(s"Res = $res")
    println(s"Total time = $totalTime (sec)")
    println(s"Average time = ${totalTime / repeat} (sec)")
    println(s"End of $s")
  }

  val size = 1000000
  val vects = new VectorOfVector2D(size)
  for (i <- 0 until size) {
    val d = (1.0 * i) / size
    vects(i) = Vector2D(d, d)
  }
  val transf = Transform2D(1.0, Vector2D(-2.5, 5.0))
  run("Warm Up", vects, transf)
  println()
  run("Actual", vects, transf)
}

class VectorOfVector2D(initialSize: Int) {
  private var size = initialSize
  private var buffer = new Array[Double](2 * initialSize)

  def length = size

  def apply(i: Int) = Vector2D(buffer(2 * i), buffer(2 * i + 1))

  def update(i: Int, v: Vector2D) {
    buffer(2 * i) = v.x
    buffer(2 * i + 1) = v.y
  }
}

kamre ★★★
() автор топика

Выкинул твой метод с массивами (сорри) и заюзал встроенный List.

Start of Warm Up
Res = ImmutableVector2D(x=0.0, y=0.0)
Total time = 5.9155095740000005 (sec)
Average time = 0.0059155095740000005 (sec)
End of Warm Up

Start of Actual
Res = ImmutableVector2D(x=0.0, y=0.0)
Total time = 5.891656565000001 (sec)
Average time = 0.005891656565000001 (sec)
End of Actual

при использовании мутабельных векторов - время увеличивается до 9-10 секунд

кот: http://pastebin.com/8tsrVSDx

stevejobs ★★★★☆
()
Ответ на: комментарий от kamre

А плюсы сколько на этом же компе показывают?

Res = vector2d(-2.65058e+006,5.69089e+006)
Total time = 6.871 (sec)
Average time = 0.006871 (sec)

MSVS 2012 x64, release configuration x64

stevejobs ★★★★☆
()
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от kamre

да неважно. ТВОЙ код - вот это epic win:

Start of Warm Up
Res = Vector2D(-2650584.18888554,5690885.954451363)
Total time = 1.9507224740000002 (sec)
Average time = 0.0019507224740000003 (sec)
End of Warm Up

Start of Actual
Res = Vector2D(-2650584.18888554,5690885.954451363)
Total time = 1.93952919 (sec)
Average time = 0.00193952919 (sec)
End of Actual

(правда что там написано - я не читал, возможно там что-то тоже неправильно посчиталось =)

stevejobs ★★★★☆
()

Переписать код на Java, чтобы он не тормозил и не жрал память

Я такого толстого заголовка ещё никогда не видел! 5 баллов!

DELIRIUM ☆☆☆☆☆
()

ведь Java должна быть быстра почти как C++

что ты делаешь? хахаха! прекрати!

nanoolinux ★★★★
()

ведь Java должна быть быстра почти как C++

кому должен?

quest ★★★★
()
Ответ на: комментарий от ovk48

Java/Scala стабильно отстают на несколько процентов от C++:

C++(g++ 4.8.0 -O2):
  Res = vector2d(-2.65058e+006,5.69089e+006)
  Total time = 7.604 (sec)
  Average time = 0.007604 (sec)

Java(jdk 1.7.0_15-b03 -server):
  Res = Vector2D(x=-2650584.18888554, y=5690885.954451363)
  Total time = 7.765072062000001 (sec)
  Average time = 0.007765072062000001 (sec)

Scala(2.10.2 jdk 1.7.0_15-b03 -server):
  Res = Vector2D(-2650584.18888554,5690885.954451363)
  Total time = 8.019780425 (sec)
  Average time = 0.008019780425000001 (sec)

kamre ★★★
() автор топика
Ответ на: комментарий от stevejobs

Лол, ты попробуй шлангом или гцц но без -fPIC

bhfq ★★★★★
()

Наверняка что-то сделал не так в Java варианте (ведь Java должна быть быстра почти как C++). Подскажите, как нужно правильно переписать Java вариант, чтобы приблизиться к C++?

годно вбросил.

emulek
()
Ответ на: комментарий от bhfq

Доо, какой замечательный ответ, такая глубокая мысль, жалко что только бесполезный совсем.

из твоего высера я тоже ничего нового не узнал. В т.ч. и про тебя.

emulek
()
Ответ на: комментарий от kamre

а не пачка нечитаемых формул на пол экрана (которые должен будет в итоге сгенерировать компилятор где-то там у себя внутри).

Как в Java решается эта задача?

тонко...

emulek
()
Ответ на: комментарий от anonymous

мало. вариант ТС:

qnikst@thinkpad ~/tmp/lor/2d $ \time ./a.out 
Res = vector2d(-2.65058e+06,5.69089e+06)
Total time = 3.56 (sec)
Average time = 0.00356 (sec)
3.57user 0.01system 0:03.58elapsed 99%CPU (0avgtext+0avgdata 66656maxresident)k
0inputs+0outputs (0major+4280minor)pagefaults 0swaps

это 3560 в тех же единицах.

qnikst ★★★★★
()
Ответ на: комментарий от dave

конечно :), только как всегда не честно, т.к. в эта задача фьюзится полностью в итоге выделений памяти нет вообще. Плюсы тоже можно так переписать, тогда они выиграют.

qnikst ★★★★★
()

Задача на которой Java просто обязана выдавать сравнимый с GСС результат. Дальше, как мне кажется уже надо делать на асме, чтобы эффективно использовать регистры сопроцессора. Или это уже не актально? :) Последний раз так делал, после того как сам впаял кроватку для сопроцессора на метеринскую плату.

vromanov ★★
()
Ответ на: комментарий от parrto

О как! И кроватку впаивать для этого не нужно? Само работает? Или надо покупать отдельно?

vromanov ★★
()
Ответ на: комментарий от dave

один из вариантов deforestation основанный на правиле stream/unstream. Грубо говоря - удаление промежуточных структур.

[1] https://dl.acm.org/citation.cfm?id=165214 статья от SPJ про другой вариант, основанный на foldr/build.

[2] https://dl.acm.org/citation.cfm?id=165214 статья от DonS'а, немного устарело, но принципы сохранились, используется в Text и Vector.

Впрочем подобные фишки и у Бёрда рассматривались. Собранная инфа есть в тезисе Duncan Coutts, но я так и не осилил найти, чтобы прочитать. Оба метода не покрывают всевозможные оптимизации, так что использовать логично и то и другое.

P.S. поправил ошибку.

qnikst ★★★★★
()
Последнее исправление: qnikst (всего исправлений: 1)
Ответ на: комментарий от bhfq

Ну вот можно свести 0.16 секунды на работу jvm?

По идее не должна уже JVM вмешивать в сгенерированный машинный код после warm up. Но там есть аллокация:

Vector2D res = new Vector2D(0, 0);
от которой нельзя избавиться с escape analysis. Можно передавать res снаружи и только заполнять внутри calc, раз уж Vector2D стал mutable.

kamre ★★★
() автор топика
Ответ на: комментарий от kamre

Java/Scala стабильно отстают на несколько процентов от C++

Лол, ну я думаю, это достаточно хорошо для «скриптового языка, интерпретируемого тормозной и жручей виртуальной машиной», как выражаются некоторые ущербные.

ovk48 ★★★
()
Ответ на: комментарий от qnikst

clang дает местами иногда такой код, что авторы gcc должны только радоваться:

return vector2d(v0.x + v1.x, v0.y + v1.y);

gcc
5,3%	+ 0xcd		faddp st4, st0
7,4%	+ 0xdf		faddp st2, st0

clang
8,7%	+ 0xee		addsd xmm2, xmm6
21,6%	+ 0xf2		movaps xmm6, xmm2
8,6%	+ 0xf5		movaps xmm2, xmm3
0,1%	+ 0xf8		movaps xmm3, xmm1
0,1%	+ 0x10f		addsd xmm2, xmm4

или плакать:

double x = v.x*t.cos - v.y*t.sin + t.tx;

gcc
8,4%	+ 0xa8		fld qword [eax]
0,2%	+ 0xad		fld qword [eax+0x8]
13,0%	+ 0xb0		add eax, 0x10
19,4%	+ 0xcf		fmulp st2, st0
0,4%	+ 0xd1		fmul qword [0x8048c20]
2,2%	+ 0xd7		fsubp
0,2%	+ 0xd9		fsub dword [0x8048c34]

clang
8,2%	+ 0xc0		movsd xmm4, [esi+ecx-0x8]
0,1%	+ 0xc6		movsd xmm5, [esi+ecx]
	+ 0xfb		mulsd xmm5, xmm0
2,7%	+ 0xff		mulsd xmm4, xmm3
5,9%	+ 0x103		subsd xmm4, xmm5
	+ 0x107		addsd xmm4, [0x8048e28]
bhfq ★★★★★
()
Ответ на: комментарий от qnikst

с -O3

$ g++ -O3 -m64 cpptest.cpp 
$ ./a.out 
Res = vector2d(-2.65058e+06,5.69089e+06)
Total time = 3.13 (sec)
Average time = 0.00313 (sec)

$ clang++ -O3 -m64 cpptest.cpp 
$ ./a.out 
Res = vector2d(-2.65058e+06,5.69089e+06)
Total time = 2.25 (sec)
Average time = 0.00225 (sec)
bhfq ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.