【问题标题】:Processing instrument capture data处理仪器捕获数据
【发布时间】:2012-07-22 03:03:11
【问题描述】:

我有一个产生数据流的仪器;我的代码通过回调onDataAcquisitionEvent(const InstrumentOutput &data) 访问这些数据。数据处理算法可能比数据到达的速度慢得多,所以我不能希望处理每一条数据(我也不必),但希望处理尽可能多的数据。感谢仪器作为环境传感器,具有我无法控制的数据采集速率。例如,InstrumentOutput 可以是一个包含不同位置的三个同时压力测量值的类。

我还需要保留一些简短的数据历史记录。例如,假设我可以合理地希望每 200 毫秒左右处理一次数据样本。大多数时候,我很乐意只处理最后一个样本,但有时我需要查看在最新样本之前到达的几秒钟的数据,这取决于最后一个样本中是否存在异常读数。

另一个要求是尽快退出onDataAcquisitionEvent()回调,避免传感器数据丢失。

数据采集库(第三方)在单独的线程上收集仪器数据。

我想到了以下设计;拥有单个生产者/单个消费者队列,并在 onDataAcquisitionEvent() 回调中将数据令牌推送到同步队列中。
在接收端,有一个循环从队列中弹出数据。由于数据到达率很高,循环几乎永远不会休眠。在每次迭代中,都会发生以下情况:

  1. 从队列中弹出所有可用数据,
  2. 弹出的数据被复制到一个循环缓冲区中(我使用了 boost 循环缓冲区),这样一些历史记录总是可用的,
  3. 处理缓冲区中的最后一个元素(并可能查看之前的元素),
  4. 重复循环。

问题:

  1. 这种设计是否合理,有哪些缺陷?和
  2. 还有什么更好的设计?

编辑:我想到的一个问题是当循环缓冲区的大小不足以容纳所需的历史时;目前我只是重新分配循环缓冲区,使其大小加倍。我希望我只需要这样做一两次。

【问题讨论】:

  • 请说得更具体一点:当您写“数据”时,您是指实际的 PCM 样本还是某种事件,例如MIDI 事件?
  • 谢谢,“乐器”与音乐无关,它是一个环境传感器。我会更新原帖。

标签: c++ buffering circular-buffer data-acquisition


【解决方案1】:

我在数据采集方面有一点经验,我可以告诉你,很多开发人员都存在过早的特性蠕变问题。因为简单地将仪器中的数据捕获到日志中听起来很容易,所以人们倾向于在验证日志记录是否确实可靠之前向系统添加不必要的组件。这是一个很大的错误。

另一个要求是尽快退出onDataAcquisitionEvent()回调,避免传感器数据丢失。

这是产品的该部分在所有现场条件下都能 110% 工作之前的唯一要求。


大多数时候我很乐意只处理最后一个样本,但有时我需要查看在最新样本之前到达的几秒钟的数据,这取决于是否存在异常读数最后一个样本。

“大多数时候”无关紧要。为最坏情况编写代码,因为onDataAcquisitionEvent() 不能花时间考虑突发事件。

听起来您陷入了这样的陷阱:将其设计为使用可能可用的最佳数据,而如果不可用或向监视器提供最佳数据最终过于昂贵,可能会发生什么.

从源头抽取数据。指定异常案例处理需要多少样本,并尝试以恒定的采样率提供那么多样本,再加上大约 20% 的余量。

当然不应该有永不休眠的循环。循环缓冲区很好,但只需用您需要的最小值填充它,并只根据需要频繁地对其进行分析。

系统的质量是由其稳定性和确定性决定的,而不是试图加倍努力并提供尽可能多的东西。

【讨论】:

  • 谢谢。关于不提供太多数据的好点;很有可能采集率变得如此之高,以至于我将大部分时间都花在将已经不相关的数据放入缓冲区中。
【解决方案2】:

您的生产者/消费者设计正是正确的设计。在实时系统中,我们通常还为消费线程提供不同的运行时优先级,不确定这是否适用于您的情况。

使用基本上是双向链表的数据结构,这样如果它增长,您不需要重新分配所有内容,并且您还可以 O(1) 访问所需的样本。

如果您的内存不够大,无法保存几秒钟的数据(它应该 - 每 200 毫秒一个样本?每秒 5 个样本。)那么您需要看看您是否可以承受从辅助内存中读取数据,但这是吞吐量,在您的情况下,与您的设计和“尽快退出回调”的要求无关。

考虑一个不需要锁定的队列实现(记住:只有单个读取器和单个写入器!),这样您的回调就不会停止。

如果您的回调非常快,请考虑禁用中断/给予它高优先级。如果它永远不会阻塞并且设置了正确的优先级,则可能没有必要。

【讨论】:

  • 谢谢。一个小的修正——处理算法最多需要 200 毫秒才能在没有过载的系统上运行,但数据到达率要高得多(这意味着我必须跳过样本)。我不必处理所有样本,但需要有历史记录以防万一。
  • 没问题。不过,我的回答仍然有效。似乎您可以在内存中容纳您需要的所有样本,正确的设计仍然是添加到链表的末尾,并让另一个线程处理样本。 (在那个线程中,你可以做任何你想做的事情,包括产生更多线程等)
【解决方案3】:

问题,(1) 这种设计听起来不错,有什么缺陷,(2) 什么是更好的设计。谢谢。

是的,它是健全的。但是出于性能原因,您应该设计代码,以便它在每个处理阶段处理一组输入样本,而不是每个处理一个样本。这会为当前最先进的 CPU 生成更优化的代码。

这样一个数组(=一块数据)的长度要么是固定的(代码更简单),要么是可变的(灵活,但某些处理可能会变得更复杂)。

作为第二个设计选择,您可能应该忽略此架构级别的历史,并将该功能降级...

大多数时候我很乐意只处理最后一个样本,但有时我需要查看几秒钟的数据 [...]

也许,跟踪历史记录应该只在代码的特殊部分实现,偶尔需要访问它。也许,这不应该是“整体架构”的一部分。如果是这样,它就完全简化了处理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    • 1970-01-01
    • 2022-08-14
    • 2014-04-13
    • 1970-01-01
    相关资源
    最近更新 更多