LINUX.ORG.RU

Тест java vs nodejs по жору памяти

 , , ,


1

1

java:

package test_java;

import java.util.HashMap;
import java.util.Scanner;

public class Main {

    class Table {
        Long x2;
        Long x3;
        String str;
        public Table(Long i) {
            x2 = i*2;
            x3 = i*3;
            str = x3.toString();
        }
    }
    private HashMap<Long, Table> hash = new HashMap<Long, Table>();

    public Main() {
    
    }

    private void log(String s) {
        System.out.println(s);
    }

    public void test() {
        for (long i = 0; i < 1000L*1000L; i++) {
            hash.put(i, new Table(i));
        }
        log("Generated");
        Scanner scan = new Scanner(System.in);
        scan.nextInt();
    }

    public static void main(String[] args) {
       Main m = new Main();
       m.test();
    }
}

Nodejs:
h = {}
for (i=0; i < 1000*1000; i++) h[i] = {x2:i*2, x3:i*3, str : (i*3)+""}
Жор памяти с htop после нескольких минут простоя: https://i.imgur.com/6VY2zqfl.png

Ъ: 267мб - ява, 127 нода (было ~180 сразу после запуска)

И что получается? Ява сосуна по памяти больше чем в 2 раза? (и во много раз по коду, лол) Как дальше жить? Что я не так сделал?

$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

$ node --version
v10.14.2


Последнее исправление: crutch_master (всего исправлений: 2)

ёлы-палы… сравнивать надо по количеству ынтырпрайз-фреймворков

bvn13
()

Поменяй class Table на:

static final Table {
    long x2;
    long x3;
    String str;
    public Table(Long i) {
        x2 = i*2;
        x3 = i*3;
        str = x3.toString();
    }
}

static - минус одна ссылка на обрамляющий класс. Ну и примитивы для хранения 8 байтных значений, вместо ссылок на объекты Long в куче.

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

У жручего разница вообще в ~10 раз. Я охренел и пошел писать минимальный пример.

crutch_master
() автор топика
Последнее исправление: crutch_master (всего исправлений: 1)

Короче я всё равно хз, как дальше жить. В примитивы всё и везде не завернёшь, а если и завернёшь, все равно в память насрут из либ. Буду думать о том, чтобы приделать оракляный клиент к ноде. Как-то так.

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

Т.е. оно не всегда транслирует? Или VM это не Virtual Machine, а наличие GC?

xaizek
()
Ответ на: комментарий от orm-i-auga

Какие ещё микрооптимизации нужно применить, чтобы джава хотя бы сравнялась по памяти с говнокодом на джаваскрипте?

Deleted
()
Ответ на: комментарий от crutch_master

Не знаю как здесь js отработает, но сделай тогда хотя бы

for (long i = Long.MAX_VALUE / 2; i < MAX_VALUE / 2 + 1000L*1000L; i++) {...}
, и аналогично в js, чтобы там тоже явно long был.

orm-i-auga
()
Ответ на: комментарий от crutch_master

Снаружи наверное квадратные скобки, не фигурные? Или ты в числоключи суешь?

Ну а собственно в чем удивление-то твое? 3млн таких объектов, 299млн байт, 100 байт на объект (плюс минус рантайм). В этой структуре три ключа и три обьекта из одного ключа. Ни один не расширялся, чтобы иметь запас capacity. Грубо 100 делим на 6 значений + 6 ключей и получаем 8.33 байт на value, как и ожидалось чуть больше 8. Жс plain value язык, это же не питон, где всё есть тазик с картошкой.

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

Правильно спросить, как вообще вм относится к структурам данных и их расположению в памяти.

anonymous
()

Я не умею в оба два, но кажется, что в джавовом сниппете есть какое-то i/o в отличии от жыэсь.

Второй момент - дефолтные настройки джавамашины разумеется не нацелены экономить сотни мегабайт памяти.

Для чистоты эксперимента я бы замерил не только потребление памяти но и скорость выполнения кода. Ну и вывод у приложений должен быть одинаковый. Jit в ноде мог вообще твой цикл выкинуть учитывая что там нет сайдэффектов итого имеем голый бинарь ноды vs java которая производит i/o поэтому не может эти циклы оптимизнуть.

pon4ik
()

Запускай с -Xmx120M (или любое значение меньше, пока не начнёт падать с OOM). Замени все Long на long. Используй нормальные коллекции вместо дефолтных. Используй массивы примитивов вместо хештаблицы.

Legioner
()
Последнее исправление: Legioner (всего исправлений: 4)
Ответ на: комментарий от Deleted

На практике достаточно не писать говнокод. Для специально заточенных под ноду примеров придется делать микрооптимизации, да.

Legioner
()

попробуй на OpenJ9, грят там существенно меньше памяти ест

Deleted
()

Уложился в 21 мегабайт.

package test;

import java.util.Scanner;

public class Test {
    int[] x2s;
    int[] x3s;
    StringBuilder strSrc;
    int[] strStarts;

    void test() {
        x2s = new int[1000_000];
        x3s = new int[1000_000];
        strSrc = new StringBuilder(6_629_626);
        strStarts = new int[1000_000];
        for (int i = 0; i < 1000_000; i++) {
            x2s[i] = i * 2;
            int x3 = i * 3;
            x3s[i] = x3;
            strStarts[i] = strSrc.length();
            strSrc.append(x3);
        }
        System.out.println(strSrc.length());
        Scanner scan = new Scanner(System.in);
        scan.nextInt();
    }

    public static void main(String[] args) {
        new Test().test();
    }
}

Запускать с -Xmx21m на последней JVM. Жду аналога на ноде.

Legioner
()
Последнее исправление: Legioner (всего исправлений: 2)
Ответ на: комментарий от Deleted

Под number отводится 64 бита, как и под long в джаве.

А сколько значащих бит для хранения целых? Тоже 64 бита? Или 53?

i-rinat ☕☕☕☕
()
Ответ на: комментарий от i-rinat

В JavaScript все числа трактуются как double. Под мантиссу 52 бита, поэтому целые числа точно представляются до 2^52. Точней до 2^53, откуда ещё бит взялся хз.

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

откуда ещё бит взялся хз.

У 0 особое представление. У всех остальных чисел старший бит всегда единичка. Её опускают, хранят только оставшиеся 52 бита.

i-rinat ☕☕☕☕
()

У меня выходит разница в 14% по потреблению памяти, но в пользую node.

const readline = require('readline');

const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
});

h = {}
for (i=0; i < 1000*1000; i++) h[i] = {x2:i*2, x3:i*3, str : (i*3)+""}

rl.question("Generated " + Object.keys(h).length, ()=> {
    rl.close()
})
node - 11.10.1: 201.876 МБ

node - 6.11.1: 213.240 МБ

import java.util.HashMap;
import java.util.Scanner;
import java.util.Map;

public final class Table {

    final long x2;
    final long x3;
    final String str;
    Table(long i) {
        x2 = i*2;
        x3 = i*3;
        str = "" + x3;
    }

    public static void main(String[] args) {
        Map<Long, Table> hash = new HashMap<>();
        for (long i = 0; i < 1000_000L; i++) hash.put(i, new Table(i));

        System.out.println("Generated");
        new Scanner(System.in).nextLine();
    }
}

Java 11: 241.984 МБ

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

Весь менстрим юзает нан боксинг, так что 64 там взять негде, разве что двойным боксом, но афаик такого нет и не нужно.

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

Если в Java коде добавить вызов к GC

  for (long i = 0; i < 1000_000L; i++) hash.put(i, new Table(i));
+ System.gc(); // << Вызов к GC
  System.out.println("Generated");
то потребление памяти становится 217.860 MB.

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

С Xmx22m не запустилось Работает если -Xmx29m

$ java -version java version «1.8.0_202» Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

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

А какая разница? Говно с GC и говно с GC. От особенностей GC и настроек может плавать на порядок как в одну, так и в другую сторону.

slovazap
()

Правдоподобно. Что не так?

Единственное что у тебя правда типы boxed там где не надо

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

Снаружи наверное квадратные скобки, не фигурные?

Фигурные, числоключи. Тут беда не в контейнере.

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

Я не измеряю скорость, чисто структуры. Явасервис сожрал 1,4гб против 200мб у ноды и имели они и те же данные. Я немного пригорел.

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

У ноды еще отрабатывет gc и она возврашает память ос через несколько минут. 30 мб - рантайм на больших дистанциях это не сильно заметно. Лично у меня вталкивает в прострацию еще то, что на js писать код в разы легче, особенно рефлексию, даже при учёте проверок корректности получаемых данных.

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

Я немного пригорел.

От чего? Java жрет сколько дают. Поставь ей -Xmx100m или совсем -Xmx50m и померяй еще раз.

Еще скажи спасибо что с такими подходами они наконец-то починили что java теперь смотрит доступную память своей cgroup, а не машины. Теперь будет уважать то что докер дает

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

Дефолт явы рассчитан что бы жить в стойке с океанчиком памяти. А дефолт нодыжс на то, что бы в студенческой поделке на электроне шкондыбать не выбешивая в конец эндюзера.

Как заверяют ораторы выше - без тонкой настройки платформ на заданные нефункциональные требования - эти замеры фуфел. Это типичная ошибка любителей подобных бенчмарков - сравнить тигрёнка и котёнка в первые месяцы жизни. А то, что у каждой платформы слегка разный целевой дефолт - это их не колышет, только собственная правота (не в твой огород камень а обобщение).

Это всё равно, что говорить что под виндой aio TCP сокеты медленнее(лэйтенси) чем под ляликс, не настроив планировщик и буфера. А после настройки внезапно оказывается, что разница в пределах погрешности, просто в винде приоритет по дефолту отдаётся рисованию окошек, да.

pon4ik
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.