尝试重新使用您的 SimState 对象(使用某种池机制),而不是每次都分配它们。经过几次模拟循环后,重新使用的 SimState 对象的向量将增长到所需的大小,从而避免重新分配并节省时间。
实现池的一种简单方法是首先将一堆预先分配的 SimState 对象推送到std::stack<SimState*>
。请注意,堆栈比队列更可取,因为您想要获取缓存中更有可能“热”的 SimState 对象(最近使用的 SimState 对象将位于堆栈的顶部)。您的模拟队列将 SimState 对象从堆栈中弹出,并用计算出的 SimState 填充它们。然后,这些计算出的 SimState 对象被推送到生产者/消费者队列中以提供给 GUI 线程。由 GUI 线程渲染后,它们被推回 SimState 堆栈(即“池”)。在执行所有这些操作时,尽量避免不必要地复制 SimState 对象。在“管道”的每个阶段直接使用 SimState 对象。
当然,您必须在 SimState 堆栈和队列中使用正确的同步机制以避免竞争条件。 Qt 可能已经有线程安全的堆栈/队列。如果存在大量争用,无锁堆栈/队列可能会加快速度(英特尔线程构建模块提供了此类无锁队列)。考虑到计算 SimState 大约需要 1/50 秒,我怀疑争用会成为问题。
如果您的 SimState 池耗尽,则意味着您的模拟线程太“超前”并且可以等待一些 SimState 对象返回到池中。模拟线程应该阻塞(使用条件变量),直到 SimState 对象在池中再次可用。 SimState 池的大小对应于可以缓冲的 SimState 数量(例如,约 50 个对象的池可为您提供长达约 1 秒的紧急处理时间)。
您还可以尝试运行并行模拟线程以利用多核处理器。这线程池 http://en.wikipedia.org/wiki/Thread_pool_pattern模式在这里很有用。但是,必须注意计算出的 SimState 必须按正确的顺序排队。按时间戳排序的线程安全优先级队列可能在这里起作用。
这是我建议的管道架构的简单图:
(右键单击并选择查看图像以获得更清晰的视图。)
(注意:池和队列通过以下方式保存 SimStatepointer,不是按值!)
希望这可以帮助。
如果您打算重复使用您的 SimState 对象,那么您的Simulation::getAgents
方法将是低效的。这是因为vector<Agent>& a
参数可能已经有足够的容量来容纳代理列表。
您现在这样做的方式会丢弃这个已经分配的向量并从头开始创建一个新的向量。
国际海事组织,你的getAgents
应该:
void Simulation::getAgents(std::vector<Agent> &a) const
{
a = mAgents;
}
是的,您会失去异常安全性,但您可能会获得性能(特别是使用可重用的 SimState 方法)。
另一个想法:您可以尝试使用 c 样式数组(或boost::array
) 和“count”变量代替std::vector
对于代理的浮动列表成员。只需使固定大小的数组足够大以适应模拟中的任何情况即可。是的,你会浪费空间,但你可能会获得很多速度。
然后,您可以使用以下方式汇集您的代理:固定大小的对象分配器(例如boost::pool http://www.boost.org/doc/libs/release/libs/pool/doc/index.html)并通过指针传递它们(或shared_ptr
)。这将消除大量的堆分配和复制。
您可以单独使用这个想法,也可以与上述想法结合使用。这个想法似乎比上面的管道更容易实现,所以你可能想先尝试一下。
还有另一个想法:您可以将模拟分解为多个阶段,并在其自己的线程中执行每个阶段,而不是使用线程池来运行模拟循环。生产者/消费者队列用于在阶段之间交换 SimState 对象。为了使其有效,不同阶段需要具有大致相似的 CPU 工作负载(否则,一个阶段将成为瓶颈)。这是利用并行性的不同方式。