【发布时间】:2012-07-04 09:06:42
【问题描述】:
在一些 C++ 项目中,我正在处理表示各种性质图形的对象。它们都实现了一个通用的抽象接口,我将在这篇文章中将其简化为一个函数:
class graph {
public:
...
// Create an iterator over the successors of v
virtual succ_iterator* succ(const vertex* v) const = 0;
...
};
succ_iterator 类也是一个抽象类,它可以迭代v 的后继顶点:
class succ_iterator {
public:
virtual void first() = 0; // reset the iterator
virtual void next() = 0; // move to next successor
virtual bool done() const = 0; // no more successor left?
virtual const vertex* dest() const = 0; // destination vertex
};
因此,在图 g 中对 v 的所有后继者的循环将如下所示:
succ_iterator* i = g->succ(v);
for (i->first(); !i->done(); i->next()) {
// use i->dest()
}
delete i;
我经常使用这样的循环来实现图形算法。 分析代码表明它花费大量时间为这些微小的迭代器实例分配内存,所以我想听听改进的想法。
不幸的是,我的图表可能有非常不同的实现,因此每个图表可能都有自己的succ_iterator 接口实现。本质上,graph 层次结构中的每个类在succ_iterator 层次结构中都有一个对应的类。例如,可以使用std::vector 实现一个图来显式存储邻接列表(在这种情况下,succ_iterator 是std::vector::const_iterator 之上的一个简单包装器),而另一个图可能是即时计算的(在这种情况下,它的succ_iterator 实例实际上会计算 继任者,因为它的进展)。我不知道编译时图表的性质,所以这排除了基于template 的实现à la STL。
我曾考虑让每个图表处理succ_iterator 池。这似乎需要一个额外的graph::succ_release(succ_iterator*) 方法来将迭代器返回到池中而不是删除它。我见过的大多数池都是在内存级别完成的:即,当你释放一个对象时,它的析构函数被调用,但占用的内存被保留,这样下一个对象将被创建(使用就地构造函数)在同一堵塞。看起来这仍然浪费时间,因为就地构造函数必须设置一个与前一个对象相同的虚拟表指针。我认为在这样的池中,我应该将delete+new 替换为与构造函数具有相同原型的recycle() 方法。由于代码库非常大,这需要更改界面,我想在实现此之前了解其他设计理念或可能的改进。
编辑:我不使用线程。
【问题讨论】:
-
第 1 步:尝试更改
malloc的实现。大多数new实现在底层都是对malloc的准系统调用,因此切换到针对速度进行了优化的jemalloc或tcmalloc甚至可以在不触及您的程序的情况下产生必要的提升。 -
感谢您的指点。我会调查这些。到目前为止,我还没有找到任何比较 jemalloc、tcmalloc 和 glibc 的 malloc 用于单线程应用程序的基准测试。你知道吗?
-
不幸的是没有。然而,Redis 2.4 release 的亮点之一是从 glibc 切换到 jemalloc; Redis 是单线程的(大部分)。
标签: c++ oop design-patterns