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

Поменяй 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 ()

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

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

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

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

anonymous ()

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

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

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

pon4ik ★★★★★ ()

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

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 4)

Уложился в 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)
Ответ на: комментарий от 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 ()
Ответ на: комментарий от 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 ()
Ответ на: комментарий от anonymous

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

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

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

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

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

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

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

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

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

pon4ik ★★★★★ ()