自己动手做 GC (1) - 续

正文中,我们用的是 std::vector<>。众所周知,std::vector<> 是 STL 中数组的类似物,适合顺序存储与访问,并不适合随机增删。所以,应该使用 std::list<>,因为哪个变量要被回收几乎是不可预知的。或者采用改进的方案:更新相对频繁的 Generation 0 和 Generation 1 采用 std::list<>,相对稳定的 Generation 2 采用 std::vector<>


各位应该注意到在 Windows 上运行 100 KB×10000 个单元的测试程序会带来严重的内存“泄露”问题。我以为是 STL 容器或者 new/delete 操作符的问题,就写了一个纯 C 的版本,但是现象依然如前。后来我发现,如果是 10000 KB×100 个,则会几乎完全回收。在分配内存总量不变的情况下,前者剩余 50 MB 左右,后者几乎为 0。

我们知道,在 CLR 中有一个大对象堆(Large Object Heap),回收策略与小对象堆不一样。

我们也知道,在使用 malloc() 分配内存时(Debug 配置),获得的指针指向的区域会被初始化(用于 CRT 内存检查),反倒是调用 HeapAlloc() 这个 API 的时候才会真正刚刚被操作系统分配内存。

根据现象与部分原理推测,既然 malloc()free() 都是 VC 运行时提供的,内部是不是也有一个不同的策略,让大于某个值的申请空间回收会更彻底呢?

上面的引文中有一句话,说 VCRT 10 下的 free() 操作后状态为:

The memory returned by the free is returned to OS, but it stays committed.

关于 Windows 的分页机制我不是很了解,大概只知道是一个 4 KB 对齐、用于常用/不常用内存交换的东西。页的状态对系统对页的操作有何影响,先查阅资料再说吧。

用 2000 个单元进行测试。在我的机器上(Windows 8.1 x64,12 GB)发现在每个单元 500 KB~600 KB 之间回收结果发生了一个突变。我根据 4 KB 对齐猜测阈值可能是 512 KB,但是最终测试的结果是在 507 KB~508 KB 之间。由于这个值可能受当前分配代码导致的页内容的排布影响而变化(例如,32 位下 GC_RECORD 不计编译器附加字段,占用空间 12 B;若是其他的大小,应该会影响空间的页对齐,从而使此值离开上面的区间),所以我认为没法获得一个准确值。

此外我还观察到,10000 KB×100 个和 100 KB×10000 个,实际占用的空间不同,后者比前者多大约 4 MB。这也证实了,编译器会添加附加信息(尽管可能不在代码所用的数据区中)。

分享到 评论