【问题标题】:ideas for speeding up a design based on abstract iterators基于抽象迭代器加速设计的想法
【发布时间】: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_iteratorstd::vector::const_iterator 之上的一个简单包装器),而另一个图可能是即时计算的(在这种情况下,它的succ_iterator 实例实际上会计算 继任者,因为它的进展)。我不知道编译时图表的性质,所以这排除了基于template 的实现à la STL。

我曾考虑让每个图表处理succ_iterator 池。这似乎需要一个额外的graph::succ_release(succ_iterator*) 方法来将迭代器返回到池中而不是删除它。我见过的大多数池都是在内存级别完成的:即,当你释放一个对象时,它的析构函数被调用,但占用的内存被保留,这样下一个对象将被创建(使用就地构造函数)在同一堵塞。看起来这仍然浪费时间,因为就地构造函数必须设置一个与前一个对象相同的虚拟表指针。我认为在这样的池中,我应该将delete+new 替换为与构造函数具有相同原型的recycle() 方法。由于代码库非常大,这需要更改界面,我想在实现此之前了解其他设计理念或可能的改进

编辑:我不使用线程。

【问题讨论】:

  • 第 1 步:尝试更改 malloc 的实现。大多数new 实现在底层都是对malloc 的准系统调用,因此切换到针对速度进行了优化的jemalloctcmalloc 甚至可以在不触及您的程序的情况下产生必要的提升。
  • 感谢您的指点。我会调查这些。到目前为止,我还没有找到任何比较 jemalloc、tcmalloc 和 glibc 的 malloc 用于单线程应用程序的基准测试。你知道吗?
  • 不幸的是没有。然而,Redis 2.4 release 的亮点之一是从 glibc 切换到 jemalloc; Redis 是单线程的(大部分)。

标签: c++ oop design-patterns


【解决方案1】:

您可以缓存每个图实例的 succ_iterators 并在需要时查找和重用它们?

//static or member:
std::map<graph*,succ_iterator*> graph_iterator_cache;

但我不知道你是否有线程安全问题。

【讨论】:

  • 我不使用任何线程(至少现在是这样)。我不确定我是否理解您的建议:上面的地图只会将一个迭代器与每个图表相关联。我需要能够使用多个迭代器。通常,DFS 可以使用一堆迭代器来实现(每个迭代器代表一个顶点的出边集合中的一条边)。有时我什至需要在同一个顶点的后继节点上使用两个迭代器,但要查看不同的边(这排除了一个实现,即所有迭代器都将为每个顶点预先分配)。
猜你喜欢
  • 1970-01-01
  • 2011-04-27
  • 2014-04-16
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-20
  • 2018-12-01
相关资源
最近更新 更多