Qt: 实现类似示波器实时绘图的最佳方法

7

我正在为Qt开发一个Gui模块,用于绘制实时测量数据,类似于数字示波器,基于Qwt。目前一切正常,但可能还有一些功能需要添加 ;-)

目前,数据以列方式存储在QVectors中,与另一个独立的QObject中的全局时间参考QVector一起存储。因此,可以按行丢弃数据以仅保留一定过去的Measurement。所有QVectors始终具有相同的长度。然后可以按行进行时间正确的绘图在QwtPlot中。

我希望更加封装数据存储,以使其更独立于测量工作。因此,很好将每个测量的时间坐标单独放在一个QObject中,并将它们与数据一起放入另一个QObject中,该QObject接受并传递数据。然后会有10或20个这样的QObjects,每个数据通道一个,由覆盖在QwtPlot上方的QObject分别绘制。

现在,数据可以是动态的,无论数据在存储、修改或之间丢弃的方式如何,都不应对外部可见。

我的问题是:这样做明智吗?20或30个QObject,每个包含10000个测量值、10000个时间值,以及一个类似大小的单独内存区域(动态填充),其中子集数据用于绘图...是否明智地将每个测量值作为信号发送到其QObject中,每秒约1kHz的频率?信号/槽机制来自于将每个OBject后期设置为QThread,并在数据上实现实时过滤,如低通或FFT - 因此,在多线程环境中,信号/槽连接很方便来控制输出?
如何在我的OBjects中高效地存储数据?我考虑使用两个QList,一个用于时间,另一个用于宝贵数据。然后动态分配两个纯double数组进行动态访问,将指针与长度一起放入结构体中,并通过accessData(pastTime)方法返回。动态内存填充了从“现在”到过去某个点的timeVal/测量值组合,可以通过信号设置。所有易碎物品都由QObject中的互斥体保护。
当丢弃旧值时,QList必须从开头开始搜索第一个年轻到足以保留的值,位于此索引之前的值将被丢弃。QMap是否更聪明,因为它有upperBound()函数?我认为隐藏的开销不值得。
一个专业的程序员如何优雅、高效地解决这个问题?有没有特殊的Qt功能需要了解?或者甚至有免费的解决方案?无论如何,这样一个基本的问题需要这么多的文本...感谢您阅读到这里 ;-)
提前感谢
Marvin
编辑:在stijn的评论后进行了一些整理。

不是很理解你的问题:为什么要在 QObject 中放置一个测量,并保留额外的 QObject 用于时间坐标?这两者都是静态数据,所以我不明白为什么你要连接信号/槽到它们,而且我觉得你想要将一个测量从其时间中分离出来听起来非常奇怪。你能否详细说明一下/提供一个关于此类对象上信号的示例? - stijn
无论如何,我以前有一些使用半实时绘图和QwtPlot的经验。让这对我们起作用的最方便的方法是生产者/消费者场景。简而言之,生产者线程从ADC读取数据并进行一些处理,然后将数据包放入循环缓冲区中。消费者线程从缓冲区中读取大块数据,决定如何处理它,切割信号并传递给QwtPlot。请注意,所有数据采集、缓冲、处理都是不使用Qt编写的:它没有真正可用于存储2D数据或与音频硬件通信的类型,因此这些都是使用C++自己编写的。 - stijn
嗯,不是这样的——我想把同一种类的所有测量值和所有记录时间放在一个子类/对象中,但隐藏这种复杂性。这样可以实现异步记录、实时处理等功能... - marvin2k
2个回答

6

photo_tom的回答已经概括了它的意思:我会避免使用QObjects来实现数据处理。

  • 如果你决定使用Qt之外的其他GUI,重构代码将更加困难。 QList和QVector这样的类可以被STL替代而没有太多问题,但是信号/插槽部分就不同了。
  • 任何第三方信号处理实现(如滤波/fft)可能会采用1D或2D数据的原始指针,因此您需要从QVector中取出这些指针,我甚至不确定是否可能。如果不行,您将不得不从QVector中取出每个样本并将其复制到内存块中,然后进行处理,再将其放回QVector中。
  • 这就带我们来到关于QList/QMap的问题:可以使用它们中的任何一个,但它们实际上是设计为具有随机访问迭代器的动态容器,而您拥有一些固定大小的内存块,其中包含2D数据。值得研究一下自定义数据容器类,以完全满足您的需求。拿起纸笔,写下您实际需要的内容,清空您的思维并忘记Qt / STL / ...,然后考虑您需要实现这些组件,然后进一步考虑这些组件如何可以实现(最终以Qt为基础)。
  • 对于这样的代码,最好不要重新分配。预先限制允许的最大历史记录和样本数(或将其设置为配置设置),并在程序启动时分配所需的数组,然后进一步重用相同的内存。
  • 考虑使用循环缓冲区。当数据以与取出数据不同的速率进入时(大多数情况下是数据采集),它非常方便,不需要重新分配,自动保留历史记录,并且如果其读/写方法直接返回指向底层内存的指针,则可以使用最少量的内存副本来实现。
  • 也许重新考虑您的线程想法,因为这可能会使您的代码变得不必要地复杂:最终,所有不同通道的数据都必须同时显示在屏幕上。如果用户在x时间看到来自通道1的数据,则通道2的数据也必须来自时间x,否则作为范围就没有太多意义。但是假设您在不同的线程中处理这些通道的数据,则在执行实际显示的线程中需要额外的同步,因为不是所有数据线程都会在时间x同时完成处理块。除此之外,请考虑可能根本没有性能提升:如果CPU必须为30个通道计算FFT并在100%处剪辑,那么它是否真的会更快地计算这些FFT,如果它们分布在30个线程中?
  • 顺便说一句,您说这是一个基本问题,我认为它实际上不是。我已经在不同设备上为数据采集/处理/可视化/保存做了一些应用程序,我认为这些应用程序最难开发..

太多开发人员没有从一开始就考虑到这些优秀的观点。我同意你最后一个观点,即这些类型的应用程序是最难的。 - photo_tom
好的,非常感谢。在我的当前解决方案中,最新的40000个元素被保存在一个类中,并且其中的一个子集会动态地提供给QwtPlot。这使得可以对“旧”数据进行回滚、缩放和过滤。现在基础功能已经实现,我正在开始使用FFT - 还在考虑线程 - 看看运行速度如何。目前,在实时情况下,它以9000Hz的音频绘制了40000个元素,同时有两个程序并行运行 - CPU占用100%。这还可以;-) 很好的建议是保持内存分配,并对代码进行一些次要的清理! - marvin2k

3

QObjects乍一看似乎是处理此类数据存储问题的绝佳方法。当我第一次开始使用Qt时,我也是这么想的。然而,它们并不是用于此目的的。关于QObjects的真实用途和作用有一个很好的介绍,可以在http://www.informit.com/articles/article.aspx?p=667415上找到。

如果你要做的只是将数据存储在一个类中,正如我阅读你的问题时所看到的那样,那么就不要使用基于QObject的类。这会严重影响你的性能。

至于哪些Qt特定的功能会对你有所帮助,实际上没有什么特别适用于Qt的东西可以帮助你。我发现Qt的容器比标准模板库或一些boost专门的容器更易于使用,因为当时我还不太了解stl。

从性能角度来看,我最好的建议是通过编写内存池系统或为每个新读取使用Boost Pool来最小化new/delete的数量,并尽量减少数据移动。


谢谢,我已经从数据存储中移除了QObject,这确实加快了速度;-)感谢您的文章,除了Qt文档之外,还有一个新的视角。但是我不打算使用boost--我正在进行一个更大的项目,不能做出这样的决定。Qt很好;-) - marvin2k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接