在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");
}
}
2条答案
按热度按时间vql8enpb1#
在Nim中,你也可以把对象放到栈上,这样就不需要垃圾收集,只占用对象中成员的空间。当把一个自动分配的对象放到堆上时,会有一些垃圾收集的内存开销,但是对象本身仍然保持其成员的大小(当然还要加上填充)。
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,甚至不计算那么多分配的成本,如果可能的话,通常应该避免。(动态)数组几乎总是比链表更可取。