D语言的垃圾回收器是否有效?

4

我尝试在Windows上运行这个程序来测试D垃圾回收器是否正常工作。

DMD 2.057和2.058 beta都会给出相同的结果,无论我是否指定-release-inline-O等参数。

代码如下:

import core.memory, std.stdio;

extern(Windows) int GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);

struct MEMORYSTATUSEX
{
    uint Length, MemoryLoad;
    ulong TotalPhys, AvailPhys, TotalPageFile, AvailPageFile;
    ulong TotalVirtual, AvailVirtual, AvailExtendedVirtual;
}

void testA(size_t count)
{
    size_t[] a;
    foreach (i; 0 .. count)
        a ~= i;
    //delete a;
}

void main()
{
    MEMORYSTATUSEX ms;
    ms.Length = ms.sizeof;

    foreach (i; 0 .. 32)
    {
        testA(16 << 20);
        GlobalMemoryStatusEx(ms);
        stderr.writefln("AvailPhys: %s MiB", ms.AvailPhys >>> 20);
    }
}

输出结果如下:

AvailPhys: 3711 MiB
AvailPhys: 3365 MiB
AvailPhys: 3061 MiB
AvailPhys: 2747 MiB
AvailPhys: 2458 MiB
core.exception.OutOfMemoryError

当我取消注释delete a;语句后,输出结果如下:
AvailPhys: 3714 MiB
AvailPhys: 3702 MiB
AvailPhys: 3701 MiB
AvailPhys: 3702 MiB
AvailPhys: 3702 MiB
...

所以我想问题很明显... GC是否真正发挥作用了?
4个回答

8
这里的问题是错误指针。D语言的垃圾收集器是保守型的,这意味着它并不总是知道什么是指针和什么不是指针。有时候它必须假设如果将位模式解释为指针,则会指向GC分配的内存。这主要是对大量分配而言的问题,因为大块内存更容易成为错误指针的目标。
每次调用testA()时,您都会分配约48 MB的内存。根据我的经验,在32位系统上几乎可以保证会有一个错误指针指向该块内存。如果您在64位模式下编译代码(Linux、OSX和FreeBSD支持,但Windows尚未支持),则可能会获得更好的结果,因为64位地址空间更加稀疏。
至于我的GC优化(我是CyberShadow提到的David Simcha),有两批。一批已经超过6个月了,没有造成任何问题。另一批还在审核中,作为拉取请求提交,尚未包含在主druntime树中。这些可能不是问题所在。
短期内,解决方案是手动释放这些巨大的块。长期来看,我们需要添加精确扫描,至少对于堆。 (精确的堆栈扫描是一个更难的问题。)我几年前写了一个补丁来实现这一点,但它被拒绝了,因为它依赖于模板和编译时函数评估来为每个数据类型生成指针偏移信息。希望这些信息最终将由编译器直接生成,这样我就可以重新创建我的精确堆扫描补丁,用于垃圾收集器。

你确定那真的是问题所在吗?首先,指针并不是随机的——它们跨越虚拟内存的低16 MiB(从地址0到地址16<<20)。我非常确定那个虚拟内存区域里什么都没有——即使有(?!),那也只能防止收集一个数组(因为每个数组都大于16 MiB),而不是整个数组。此外,除此之外,GC不应该忽略第一个位置的size_t 块吗?我认为它只会扫描可能包含指针的数据(NO_SCAN)? - user541686
@Mehrdad:两件事情:一,伪指针不是来自你分配的块。它们来自静态数据、堆栈、RTTI等等。二,GC并不会从最低地址开始分配。它会从某个任意的高地址开始。我不知道具体细节。尝试使用类似new byte [48 * 1024 * 1024] 的语句在循环中分配48 MB的内存。我敢打赌,这样做会耗费大量内存或者甚至耗尽内存,虽然需要更长的时间。 - dsimcha
你是否碰巧知道为什么这个问题被标记为“已解决无效”? - user541686

3

这似乎是一个回归问题 - 在D1(DMD 1.069)中没有发生。David Simcha最近一直在优化GC,所以可能与此有关。请提交错误报告。


哇,那么它在D2中完全被破坏了吗? - user541686
不,当然不是 - 我打赌你只是遇到了一个特殊情况或者什么问题。 - Vladimir Panteleev
有趣的是,这实际上是我尝试显式测试GC的第一件事。 - user541686

2

附注:如果在makefile中设置DFLAGS为-debug=PRINTF,重新构建Druntime,您将通过控制台获取有关GC分配/释放的信息。 :)


1

它确实可以工作。当前的实现只是从未将内存释放给操作系统。虽然GC会重用已获取的内存,所以这不是真正的泄漏。


那被抛出的 core.exception.OutOfMemoryError 是怎么回事?当我使用 delete 时,内存是如何还给操作系统的呢? - user541686
2
Perl从未将内存释放回操作系统,它已经存在了二十多年。 - Brad Gilbert
@BradGilbert:我不知道你想说什么,但我猜这也是好事吧.. - user541686
1
@Mehrdad 只是说目前它不会将内存返回给操作系统,这并不是很重要。在现代操作系统的内存处理程序中,这也并不是非常重要。 - Brad Gilbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接