2008-09-19 68 views
1
  1. 在应用程序池技术最小延迟对象,我们有一些关于30类型的对象被重复创建。
  2. 其中一些寿命长(小时)有些短(毫秒)。
  3. 对象可以在一个线程中创建并在另一个线程中销毁。

有没有人有任何线索什么可能是最好的创建/销毁延迟,低锁争用和合理的内存利用率意义上的良好池技术?在多线程应用程序

附加1.

1.1。对于一种类型的对象池/内存分配通常是不相关的另一种类型的(参见1.3除外)

1.2。内存分配在一段时间内仅针对一种类型(类)执行,通常针对多个对象。

1.3。如果一个类型使用指针聚合另一个类型(出于某种原因),这些类型在一个连续的内存中分配在一起。

附加2.

2.1。已知每个类型使用访问序列化的集合比新/删除更差。

2.2。应用程序用于不同的平台/编译器,不能使用编译器/平台特定的技巧。

追加3

变得明显,最快的(与最低的延迟)执行要组织对象池,如明星般的工厂网络。中央工厂是全球其他线程专用工厂的地方。定期的对象提供/回收在特定于线程的工厂中更有效,而中央工厂可用于线程之间的对象平衡。

3.1。组织中央工厂和特定线程工厂之间通信的最有效方法是什么?

+0

你知道时间提前的对象的生命周期,或是否会转移线程? – hazzen 2008-09-19 05:41:38

+0

不幸的是没有。这由应用程序逻辑定义。 – user18547 2008-09-19 06:29:38

回答

1

如果你没有看过tcmalloc,你可能想看一看。将您的实施置于其概念之外可能是一个好的开始。要点:

  • 确定一组大小类别。 (每个分配将通过使用从等于或大于尺寸的分配的条目被满足。)
  • 使用一个每页大小级。 (页面中的所有实例大小相同)
  • 使用每线程自由列表来避免每个alloc/dealloc上的原子操作
  • 当每线程的自由列表太大时,将一些实例移回到中央freelist。尝试从同一页面撤回分配。
  • 当每线程freelist为空时,从中央freelist拿一些。尝试采取连续的条目。
  • 重要:你可能知道这一点,但要确保你的设计将最大限度地减少错误共享。

其他事情可以做,tcmalloc不能:

  • 尝试通过更细致的分配池,使引用的局部性。例如,如果几千个对象一起被访问,那么最好是它们在内存中靠近在一起。 (为了最小化缓存错过和TLB错误。)如果你从他们自己的线程缓存中分配这些实例,那么它们应该具有相当好的局部性。

  • 如果事先哪些实例将是长期且不会,然后从单独的线程高速缓存分配他们知道。如果您不知道,则定期使用线程缓存复制旧实例进行分配,并将旧引用更新为新实例。

2

为了尽量减少结构/自毁延迟,你手头需要完全构造的对象,所以你会消除新/构造函数/析构函数/删除时间。这些“空闲”对象可以保存在列表中,因此您只需在最后弹出/推入元素。

可在锁定对象池(每个类型)一个接一个。它比全系统锁定效率更高一些,但不具有逐个对象锁定的开销。

+0

如果ctor和dtor不分配或释放内存,那么它们应该很快,因此应该没有理由避免它们。 此外,每个类型有一个freelist的问题是您必须使用原子操作来操作它,并且这会鼓励错误的共享。 – 0124816 2008-09-19 07:38:47

+0

不确定这是最佳答案。恕我直言,关于tcmalloc的一个更好。不能投票,虽然 – user18547 2008-09-19 16:29:30

3

我假设你有个人资料,并尽一切创造之后,测量你的代码,并验证了创建/销毁实际上导致的问题。否则这是你应该先做的。

如果你还想做的对象池,作为第一步,你要保证你的对象是无状态的堂妹,这将是重用对象的前提。同样,你应该确保对象的成员和对象本身没有被用于创建它的不同线程中的问题。 (COM STA对象/窗口句柄等)

如果您使用Windows和COM,则使用系统提供的池的一种方法是编写Free Threaded对象并启用对象池,这将使COM +运行时(以前称为MTS)为你做这个。如果您使用其他平台(如Java),也许可以使用定义对象应该实现的接口的应用程序服务器,并且COM +服务器可以为您进行池化。

或者你可以推出自己的代码。但是你应该尝试找出是否有图案的这一点,如果是的,这不是什么遵循以下

如果您需要推出自己的代码使用,创建一个动态可扩展的收集跟踪已创建的对象。最好使用一个矢量集合,因为你只能添加到集合中,并且很容易遍历它搜索一个空闲对象。 (假设你不删除池中的对象)。根据您的删除策略更改集合类型(如果您正在使用C++以便在同一位置删除并重新创建对象,则指向该对象的指针向量/引用)

每个对象都应使用可读取的标志进行跟踪以易变的方式进行,并使用联锁功能进行更改以将其标记为正在使用/未使用。

如果使用了所有的对象,你需要创建一个新的对象,并将其添加到集合。在添加之前,您可以获取锁(临界区),将新对象标记为正在使用并退出锁。

措施,并继续 - 或许,如果你执行上面的集合作为一个类,你可以轻松地创建不同的对象类型不同的集合,以便从做不同的工作线程减少锁争用。

最后,你可以实现一个重载类工厂的接口,它可以创建各种池对象,并知道哪些收集持有哪一类

然后,您可以在此设计优化从那里。

希望有所帮助。

0

如果有池的优选尺寸的一些猜测可以使用利用阵列(最快可能的解决方案)堆叠结构创建固定大小池。然后你需要实现对象生命期硬初始化(和内存分配),软初始化,软清理和硬清理(以及内存释放)四个阶段。现在,在伪代码:

Object* ObjectPool::AcquireObject() 
{ 
    Object* object = 0; 
    lock(_stackLock); 
    if(_stackIndex) 
     object = _stack[ --_stackIndex ]; 
    unlock(_stackLock); 
    if(!object) 
     object = HardInit(); 
    SoftInit(object); 
} 

void ObjectPool::ReleaseObject(Object* object) 
{ 
    SoftCleanup(object); 
    lock(_stackLock); 
    if(_stackIndex < _maxSize) 
    { 
     object = _stack[ _stackIndex++ ]; 
     unlock(_stackLock); 
    } 
    else 
    { 
     unlock(_stack); 
     HardCleanup(object); 
    } 
} 

HardInit/HardCleanup方法执行完整的对象初始化和销毁​​,并执行它们只有在池为空,或者被释放的对象无法适应池,因为它充满。 SoftIniti执行对象的软初始化,它只初始化可释放对象的那些方面。 SoftCleanup方法释放尽可能快速释放的对象所使用的资源或在其所有者驻留在池中时可能失效的资源。正如你所看到的,锁定是最小的,只有两行代码(或者只有几条指令)。

这四种方法可以在单独的(模板)类实现,所以你可以实现每个对象类型或使用微调操作。此外,您可以考虑使用智能指针在不再需要时自动将对象返回到其池中。

+0

与此有关的问题是,你会看到很多争夺堆栈锁。此外,假分享将存在,因为来自不同线程的分配将相邻。另外,如果分配3000个临时对象,然后分配1个永久对象,则不会回收3000个临时对象。 – 0124816 2008-09-19 07:42:29

0

您是否尝试过hoard allocator?它在许多系统上提供比默认分配器更好的性能。

0

为什么你有多个线程摧毁他们没有创建对象?这是处理对象生命周期的一种简单方法,但成本可能因使用情况而异。不管怎么说,如果你还没有开始实现这个功能,至少你可以在界面后面添加创建/删除功能,这样你可以在日后有更多的信息时测试/更改/优化它关于你的系统实际上做了什么。