缓慢的内存释放(引用计数结构)- 我的解决方案可行吗?

3

在我的程序中,我可以加载一个目录:ICatalog。

这个目录包含了许多引用计数结构(IItems、IElements、IRules等的集合)。

当我想要切换到另一个目录时, 我会加载一个新的目录, 但是自动释放前一个ICatalog实例需要时间,冻结我的应用程序2秒或更长时间。

我的问题是:

我想推迟旧的(不再使用的)ICatalog实例的释放到另一个线程中。

我还没有测试过,但我打算创建一个新线程:

ErazerThread.OldCatalog := Catalog; // old catalog refcount jumps to 2
Catalog := LoadNewCatalog(...);     // old catalog refcount =1
ErazerThread.Execute;               //just set OldCatalog to nil.

这样,我希望释放操作发生在线程中,这样我的应用程序就不会再被冻结了。
这种做法是否安全(和最佳实践)?您有类似方法的现有代码示例吗?

还有一件需要注意的事情是,你的对象可能与创建它的线程有一定的关联性。 - David Heffernan
你用分析器检测过了吗?找出那些免费代码运行缓慢的原因了吗?仅仅是堆操作可能不会导致问题,除非你一次释放了1.8GB内存,每次100字节。 - Warren P
@WarrenP 使用采样分析器,并在发布时包装OutputDebugString('SAMPLING ON')(和OFF),我发现95.05%的时间发生在ntdll.dll中...这对我没有任何有用的意义..(那么,剩下4.54%在我的可执行文件中的分析是否有趣进行优化?) - DamienD
Warren P:COM内存管理不比本地的慢吗?这是关于接口分配的。虽然不知道慢多少。 - Marco van de Voort
@DamienD:我很惊讶内存分配在ntdll.dll中花费了这么多时间。它一定在大量交换吧?你有多少物理内存? - Warren P
@Warren:似乎没有发生交换(2 Go)。此外,另一个线程中的释放不起作用。我从采样分析器中找到了更多信息[请参见图像](http://www.screencast.com/users/Idea/folders/Default/media/38d636f1-6eca-4359-a6e1-db98dd0fd738),使用“show callers”。我认为我必须重新考虑我的集合类实现。 - DamienD
2个回答

2

看起来还不错,但是不要直接调用线程的Execute方法;因为这样会在当前线程中运行线程对象的代码,而不是在线程对象创建的线程中运行。请改为调用StartResume方法。


2
我会让这样的线程在一些线程安全的队列(*)上阻塞,并将接口作为iunknowns推送到该队列中以释放。但是请注意,如果释放触及到您的内存管理器使用的锁(如全局堆管理器锁),那么这是徒劳的,因为您的主线程将在第一个堆管理器访问时阻塞。使用具有每个线程池的堆管理器,在一个线程中分配许多项目并在不同的线程中释放它可能会挫败(小)块算法的合并和重用。我仍然认为您描述的方式通常是正确的,但从理论角度来看,这表明可能存在从第二个线程通过堆管理器到主线程的链接。(*) 最简单的方法是将其添加到tthreadlist中,并使用tevent信号表示已添加元素。

我理解你的观点,但是关于线程和低级内存管理,我还是个初学者... 如果我错了,请纠正我:全局堆管理器锁可以由线程安全对象引起吗?(我的意思是,使用临界区域?) - DamienD
1
当释放时,线程安全对象会传递给内存管理器。由于内存管理器可以从多个线程调用,因此需要锁定。也许我应该这样总结答案:“如果遍历树和引用计数以及析构函数被证明是主要的减速因素,那么将其推送到线程可能会有所帮助。如果在堆管理器中释放时所做的工作(或者如果它们是COM对象,则调用COM)可能不会有所帮助。” - Marco van de Voort
尝试过了。将其推送到线程中在我的情况下没有帮助(请参见我的问题下的评论)。感谢您的解释。 - DamienD

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