如何存储和推送模拟状态,同时最大限度地减少每秒更新的影响?

2024-01-05

我的应用程序由两个线程组成:

  1. GUI线程(使用Qt)
  2. 模拟线程

我使用两个线程的原因是保持 GUI 的响应能力,同时让 Sim 线程尽可能快地旋转。

在我的 GUI 线程中,我以 30-60 的 FPS 渲染 sim 中的实体;然而,我希望我的模拟卡能够“向前推进”——可以这么说——并排队最终绘制的游戏状态(想想流视频,你有一个缓冲区)。

现在,对于我渲染的模拟的每一帧,我需要相应的模拟“状态”。所以我的 sim 线程看起来像这样:

while(1) {
    simulation.update();
    SimState* s = new SimState;
    simulation.getAgents( s->agents ); // store agents
    // store other things to SimState here..
    stateStore.enqueue(s); // stateStore is a QQueue<SimState*>
    if( /* some threshold reached */ )
        // push stateStore
}

SimState好像:

struct SimState {
    std::vector<Agent> agents;
    //other stuff here
};

而Simulation::getAgents 看起来像:

void Simulation::getAgents(std::vector<Agent> &a) const
{
    // mAgents is a std::vector<Agent>
    std::vector<Agent> a_tmp(mAgents);
    a.swap(a_tmp);
}

The Agents 本身是有些复杂的类。成员是一群ints and float和两个std::vector<float>s.

在当前的设置下,SIM 的处理速度必须比 GUI 线程的绘制速度快。我已经验证当前的瓶颈是simulation.getAgents( s->agents ),因为即使我忽略推送,每秒更新速度也很慢。如果我注释掉该行,我会发现每秒更新数有几个数量级的提高。

那么,我应该使用什么类型的容器来存储模拟的状态?我知道自动取款机上有大量的复制行为,但其中一些是不可避免的。我应该存储吗Agent*在向量中而不是Agent ?

Note:实际上,模拟不是循环的,而是使用 Qt 的QMetaObject::invokeMethod(this, "doSimUpdate", Qt::QueuedConnection);所以我可以使用信号/槽在线程之间进行通信;但是,我已经使用验证了一个更简单的版本while(1){}并且问题仍然存在。


尝试重新使用您的 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 工作负载(否则,一个阶段将成为瓶颈)。这是利用并行性的不同方式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何存储和推送模拟状态,同时最大限度地减少每秒更新的影响? 的相关文章

  • 如何为 C 分配的 numpy 数组注册析构函数?

    我想在 C C 中为 numpy 数组分配数字 并将它们作为 numpy 数组传递给 python 我可以做的PyArray SimpleNewFromData http docs scipy org doc numpy reference
  • 如何将 #ifdef DEBUG 添加到 Xcode?

    我的项目中有一些代码永远不应该在发布版本中使用 但在测试时很有用 我想做这样的事情 ifdef DEBUG Run my debugging only code endif 在 Xcode 4 中哪里添加 DEBUG 设置 我尝试将其放入
  • 将内置类型转换为向量

    我的 TcpClient 类接受vector
  • 单元测试一起运行时失败,单独运行时通过

    所以我的单元测试遇到了一些问题 我不能只是将它们复制并粘贴到这里 但我会尽力而为 问题似乎是 如果我一项一项地运行测试 一切都会按预期进行 但如果我告诉它一起运行测试 则 1 5 将通过 TestMethod public void Obj
  • 如何从 .resx 文件条目获取注释

    资源文件中的字符串有名称 值和注释 The ResXResourceReader类让我可以访问名称和值 有办法看评论吗 你应该能够得到Comment via ResXDataNode class http msdn microsoft co
  • C# Dns.GetHostEntry 不返回连接到 WiFi 的移动设备的名称

    我有一个 C 中的 Windows 窗体应用程序 我试图获取列表中所有客户端的主机名 下面给出的是 ra00l 来自此链接的代码示例 GetHostEntry 非常慢 https stackoverflow com questions 99
  • 获取 WPF 控件的所有附加事件处理程序

    我正在开发一个应用程序 在其中动态分配按钮的事件 现在的问题是 我希望获取按钮单击事件的所有事件 因为我希望删除以前的处理程序 我尝试将事件处理程序设置为 null 如下所示 Button Click null 但是我收到了一个无法分配 n
  • 如何在 Linq 中获得左外连接?

    我的数据库中有两个表 如下所示 顾客 C ID city 1 Dhaka 2 New york 3 London 个人信息 P ID C ID Field value 1 1 First Name Nasir 2 1 Last Name U
  • 使用 JNI 从 Java 代码中检索 String 值的内存泄漏

    我使用 GetStringUTFChars 从使用 JNI 的 java 代码中检索字符串的值 并使用 ReleaseStringUTFChars 释放该字符串 当代码在 JRE 1 4 上运行时 不会出现内存泄漏 但如果相同的代码在 JR
  • PyCharm - 如何挂起所有线程

    我们使用 PyCharm 5 0 1 进行多线程调试 当它在断点处停止时 只有特定线程停止 而所有其他线程继续 这使得 冻结时刻 和检查参数值以及其他线程的当前状态变得困难 当其中一个线程在断点处停止时 是否可以挂起所有线程 这在最新的 P
  • 如何将整数转换为 void 指针?

    在 C 中使用线程时 我面临警告 警告 从不同大小的整数转换为指针 代码如下 include
  • C++:.bmp 到文件中的字节数组

    是的 我已经解决了与此相关的其他问题 但我发现它们没有太大帮助 他们提供了一些帮助 但我仍然有点困惑 所以这是我需要做的 我们有一个 132x65 的屏幕 我有一个 132x65 的 bmp 我想遍历 bmp 并将其分成小的 1x8 列以获
  • 批量更新 SQL Server C#

    我有一个 270k 行的数据库 带有主键mid和一个名为value 我有一个包含中值和值的文本文件 现在我想更新表格 以便将每个值分配给正确的中间值 我当前的方法是从 C 读取文本文件 并为我读取的每一行更新表中的一行 必须有更快的方法来做
  • 如何在 Blackberry Cascades 中显示具有特定号码的电话板

    我正在使用带有 C QT 和 QML 的 Blackberry Cascades 10 Beta 3 SDK 以及 Blackberry 10 Dev Alpha Simulator 和 QNX Momentics IDE 并且我正在尝试实
  • 如何使用 Mongodb C# 驱动程序连接多个集合

    我需要将 3 个集合与多个集合合并在一起 lookup我在 C 驱动程序中尝试过 它允许我 lookup用户采集但无法执行秒 lookup用于设置集合 有人可以帮忙吗 db Transactions aggregate lookup fro
  • 等待线程完成

    private void button1 Click object sender EventArgs e for int i 0 i lt 15 i Thread nova new Thread Method nova Start list
  • std::async 与重载函数

    可能的重复 std bind 重载解析 https stackoverflow com questions 4159487 stdbind overload resolution 考虑以下 C 示例 class A public int f
  • HttpWebRequest 在第二次调用时超时

    为什么以下代码在第二次 及后续 运行时超时 代码挂在 using Stream objStream request GetResponse GetResponseStream 然后引发 WebException 表示请求已超时 我已经尝试过
  • (de)从 CSV 序列化为对象(或者最好是类型对象的列表)

    我是一名 C 程序员 试图学习 C 似乎有一些内置的对象序列化 但我在这里有点不知所措 我被要求将测试数据从 CSV 文件加载到对象集合中 CSV 比 xml 更受青睐 因为它更简单且更易于人类阅读 我们正在创建测试数据来运行单元测试 该集
  • 使用 GhostScript.NET 打印 PDF DPI 打印问题

    我在用GhostScript NET http ghostscriptnet codeplex com打印 PDF 当我以 96DPI 打印时 PDF 打印效果很好 但有点模糊 如果我尝试以 600DPI 打印文档 打印的页面会被极大地放大

随机推荐