Похож ли Nim на Java по неэффективности памяти? - PullRequest
0 голосов
/ 04 января 2019

В 64-битной Java каждый экземпляр объекта имеет тенденцию включать 192-битный заголовок , содержащий

  • указатель класса,
  • флаги и
  • замки (по 64 бита).

Это может вызвать большие накладные расходы памяти для небольших объектов.

Схожа ли ситуация для Нима? Будет ли большое приложение (где размер среды выполнения незначительным), написанное одинаково на двух языках, использовать примерно одинаковый объем памяти?


Обновление

Я провел несколько экспериментов, в которых я строил наивный односвязный список из 100M float64 элементов и многократно повторял его.

Java фактически использует 25% меньше памяти, чем Nim, согласно htop.

Полный код Nim:

type Node = ref object
    data : float64
    next : Node

echo "Running"

proc create(n : int): Node =
    var 
        tmp = Node(data: 0, next: nil)

    for i in 1..<n:
        tmp = Node(data: i.float64, next: tmp)

    return tmp


proc sum(x: Node): float64 = 
    var
        tmp: float64 = 0
        y = x

    while true:
        tmp += y.data
        y = y.next

        if y.isNil:
            return tmp


proc sums(x: Node, n: int): float64 = 
    var tmp: float64 = 0

    for i in 0..<n:
        tmp += sum(x) / n.float64

    return tmp

let x = create(1000 * 1000 * 100)

echo "Created"

echo sums(x, 100)

echo "Finished"

Используется 3,1 ГБ, что соответствует 269 битам на Node, тогда как Java использует 203 бит на Node в очень похожем коде. Это меньше чем 192-битный заголовок + 128-битная структура. Я предполагаю, что какая-то JIT-оптимизация заставляет Java работать, используя меньше памяти.

Полный код Java:

Node.java

public class Node {
    double data = 0;
    Node next = null;
}

SListTest.java

public class SListTest {

    static Node create(int n) {
        Node tmp = new Node();

        for(int i = 1; i < n; ++i) {
            Node p = new Node();
            p.data = i;
            p.next = tmp;
            tmp = p;
        }

        return tmp;
    }

    static double sum(Node x) {
        double tmp = 0;

        while(x != null) {
            tmp += x.data;
            x = x.next;
        }

        return tmp;
    }

    static double sums(Node x, int n) {
        double tmp = 0;

        for(int i = 0; i < n; ++i)
            tmp += sum(x);

        return tmp / n;
    }

    public static void echo(String s) {
        System.out.println(s);
        System.out.flush();
    }

    public static void main(String[] args) {
        echo("Started");
        Node p = create(1000 * 1000 * 100);
        echo("Created");
        double tmp = sums(p, 100);
        System.out.printf("%f\n", tmp);
        echo("Finished");
    }

}

Ответы [ 2 ]

0 голосов
/ 06 января 2019

Как для Nim, так и для HotSpot (обратите внимание, что не во всех реализациях Java требуется использовать один и тот же подход), для базовых распределений требуется одно слово для информации GC и одно слово для информации о типе ( Nim , HotSpot ). В HotSpot вы можете уменьшить информацию о типе до половины слова на 64-разрядных компьютерах, если вам не нужно более 32 ГБ пространства кучи, используя -XX:+UseCompressedOops.

Современные реализации блокировки в Java не несут дополнительных затрат; слово GC также используется для тонкой схемы блокировки и при необходимости расширяется до указателя на полный монитор. Таким образом, по умолчанию у нас есть два слова накладных расходов на объект.

В вашем примере потребление памяти на объект составляет четыре слова в Nim на 64-битной машине:

  • Два слова данных заголовка.
  • Одно слово для значения с плавающей запятой.
  • Одно слово для следующего указателя.

1e8 выделения этого размера требуют необработанного объема 32 * 1e8 = 3,2e9 байтов, т.е. около 3 ГБ.

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

0 голосов
/ 04 января 2019

В Nim вы также можете помещать объекты в стек, таким образом, не требуя сборки мусора и занимая столько же места, сколько члены в объекте. При помещении автоматически выделенного объекта в кучу возникают некоторые накладные расходы памяти для сбора мусора, но сам объект по-прежнему остается в размере его членов (плюс, конечно, заполнение).

...