有效地使用多个分配器

时间:2012-03-10 21:54:35

标签: c++ design-patterns memory memory-management architecture

我一直在研究将我的分配方法从简化重载新方法转换为通过代码库使用多个分配器。但是,如何有效地使用多个分配器?我可以通过我的研究设计的唯一方法是让分配器成为全局变量。虽然这似乎有问题,因为使用许多全局变量通常是一个“坏主意”。

我希望了解如何有效地使用多个分配器。例如,我可能只有一个分配器用于特定子系统,而另一个分配器用于不同的子系统。我不确定这样做的唯一方法是使用多个全局分配器,所以我希望有更好的洞察力和设计。

3 个答案:

答案 0 :(得分:10)

在C ++ 2003中,分配器模型被破坏,并没有真正合适的解决方案。对于C ++ 2011,分配器模型是固定的,您可以将每个实例分配器传播到包含的对象(当然,除非您选择替换它们)。通常,为了使它有用,您可能希望使用动态多态分配器类型,默认std::allocator<T>不是必需的(并且通常我希望它不是动态多态的,尽管这可能是更好的实现选择)。但是,[几乎]标准C ++库中进行内存分配的所有类都是将分配器类型作为模板参数的模板(例如,IOStream是一个例外,但通常它们不会分配任何有意义的内存量来保证添加分配器支持)。

在您的一些评论中,您坚持认为分配器实际上需要是全局的:这绝对不正确。每个分配器识别类型存储给定的分配器的副本(至少,如果它具有任何实例级数据;如果不存在则没有任何要存储的内容,例如使用{{1}的默认分配器的情况和operator new())。这实际上意味着只要存在使用它的任何活动分配器,给予对象的分配机制就需要保持不变。这个可以使用全局对象完成,但也可以使用例如引用计数或将分配器与包含给定它的所有对象的对象相关联。例如,如果每个“文档”(想想XML,Excel,Pages,无论结构文件)都将分配器传递给其成员,那么分配器可以作为文档的成员存活,并在文档被销毁后销毁所有内容后销毁。分配器模型的这一部分应该与C ++之前的2011类一起使用,只要它们也使用allocator参数。但是,在C ++ 2011之前的类中,分配器不会传递给包含的对象。例如,如果您将分配器提供给operator delete(),那么C ++ 2011版本将使用分配给std::vector<std::string>的分配器来创建std::string,该分配器被适当地转换为处理std::vector<std::string> }秒。对于预C ++ 2011分配器,这不会发生。

要在子系统中实际使用分配器,您实际上需要传递它们,或者显式地作为函数和/或类的参数,或者通过作为上下文的分配器感知对象隐式地传递它们。例如,如果您使用任何标准容器作为传递的上下文的[部分],则可以使用其std::string方法获取已使用的分配器。

答案 1 :(得分:2)

您可以使用new展示位置。这可以用于指定内存区域,也可以用于重载类型static void* operator new(ARGS)。如果效率很重要且问题很严重,那么全球不是必需的,而且这里真的是个坏主意。当然,您需要保留一个或多个分配器。

您可以做的最好的事情就是根据程序中的模式和实际使用情况了解您的问题并为分配器创建策略。通用目的malloc非常擅长它的作用,所以总是将它作为一个基线进行衡量。如果您不知道您的使用模式,您的分配器可能会慢于malloc

另外请记住,您使用的这些类型将失去与标准容器的兼容性,除非您为标准容器使用全局或线程本地和自定义分配器 - 这在很多情况下很快就会失败。另一种方法是编写自己的分配器和容器。

答案 2 :(得分:2)

多个分配器的一些用途包括减少CPU使用,减少碎片和减少缓存未命中。因此,解决方案实际上取决于您的分配瓶颈的类型和位置。

通过为活动线程提供无锁堆,消除同步,可以提高CPU使用率。这可以在具有线程本地存储的内存分配器中完成。

通过从不同的堆分配不同生命周期的分配来改进碎片 - 在用户活动任务的单独堆中分配后台IO将确保两者不会相互混淆。这可能是通过堆叠堆栈来实现的,当你处于不同的功能范围时推送/弹出。

通过将系统内的分配保持在一起,可以改善缓存未命中。让Quadtree / Octree分配来自他们自己的堆将保证在视图frustrum查询中存在位置。最好通过重载特定类(OctreeNode)的operator new和operator delete来完成。