所以我试着通过在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真的有效吗?
答案 0 :(得分:8)
这里的问题是错误的指针。 D的垃圾收集器是保守的,这意味着它并不总是知道什么是指针而什么不是。它有时必须假设指向GC分配的内存(如果被解释为指针)的位模式是指针。这对于大型分配来说主要是一个问题,因为大块是错误指针的更大目标。
每次拨打testA()
时,您的分配大约为48 MB。根据我的经验,这足以几乎保证在32位系统上会有一个错误的指针进入块。如果以64位模式编译代码(在Linux,OSX和FreeBSD上支持,但在Windows上不支持),你可能会得到更好的结果,因为64位地址空间更加稀疏。
就我的GC优化而言(我是CyberShadow提到的David Simcha),有两批。一个人已经进行了> 6个月并没有造成任何问题。另一个仍在审查作为拉取请求,并且还没有在主要的druntime树中。这些可能不是问题。
短期来说,解决方案是手动释放这些巨大的块。从长远来看,我们需要添加精确的扫描,至少对于堆来说。 (精确的堆栈扫描是一个更难的问题。)几年前我写了一个补丁来做这个,但它被拒绝了,因为它依赖于模板和编译时间函数评估来生成每种数据类型的指针偏移信息。希望这些信息最终将由编译器直接生成,我可以为垃圾收集器重新创建精确的堆扫描补丁。
答案 1 :(得分:3)
这看起来像是回归 - 它不会发生在D1(DMD 1.069)中。 David Simcha最近一直在优化GC,所以它可能与此有关。请提交错误报告。
答案 2 :(得分:2)
P.S。如果在makefile中将DFLAGS设置为-debug=PRINTF
重建Druntime,您将获得有关GC何时通过控制台分配/解除分配的信息。 :)
答案 3 :(得分:1)
确实有效。当前的实现永远不会释放内存到操作系统。虽然GC重新使用获得的内存,但它并不是真正的泄漏。