Nim的内存开销与Java相似吗?

13z8s7eq  于 2023-03-16  发布在  Java
关注(0)|答案(2)|浏览(133)

在64位Java中,每个对象示例往往包含一个192-bit header,其中包含

  • 类指针,
  • 标志和
  • 锁(每个64位)。

这可能会导致小对象占用大量内存。
Nim的情况是否类似?一个大型应用程序(运行时的大小可以忽略不计),用这两种语言类似地编写,使用大约相同的内存量吗?

更新*:*

我做了一些实验,构建了一个包含1亿个float64元素的简单单链表,并对其进行了多次迭代。
根据htop的数据,Java实际上比Nim少使用25%的内存。
完整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.1GB,也就是说每个Node使用269位,而Java在非常相似的代码中每个Node使用203位,这比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");
    }

}
vql8enpb

vql8enpb1#

在Nim中,你也可以把对象放到栈上,这样就不需要垃圾收集,只占用对象中成员的空间。当把一个自动分配的对象放到堆上时,会有一些垃圾收集的内存开销,但是对象本身仍然保持其成员的大小(当然还要加上填充)。

vlju58qv

vlju58qv2#

对于Nim和HotSpot(注意不是所有的Java实现都需要使用相同的方法),基本的分配需要一个字用于GC信息,一个字用于类型信息(Nim,HotSpot)。在HotSpot中,如果使用-XX:+UseCompressedOops不需要超过32 GB的堆空间,可以将类型信息减少到半个字。
Java中的现代锁实现不会招致额外的开销;GC字也用于瘦锁机制,如果需要的话,它会扩展为一个指针指向一个满监视器。2因此,默认情况下,每个对象有两个字的开销。
在您的示例中,在64位计算机上,每个对象的内存消耗为Nim中的四个字:

  • 两个字的标题数据。
  • 一个字表示浮点值。
  • 一个字代表下一个指针。

这种大小的1 e8分配需要原始量32* 1 e8 = 3.2e9字节,即大约3GB。
我还要补充一点,大量的小分配往往是bad for memory locality,甚至不计算那么多分配的成本,如果可能的话,通常应该避免。(动态)数组几乎总是比链表更可取。

相关问题