本文首发于 BriFuture 的 我的博客html
在个人前一篇文章 使用 Qt 获取 UDP 数据并显示成图片 中,我讲了如何用 Python 模拟发送数据,如何在 Qt 中高效的接收 UDP 数据包并将数据解析出来。然而此前的文章在分别显示 RGB 通道、R 通道、G 通道、B 通道这四组通道的图片时仍然会出现处理速度过慢的问题。python
前面说过编写的程序至少会用到 3 个线程来分别处理 UI、socket 数据、数据解析,由于不这样作无法在时限内处理完接收到的数据,写第一篇博客的时候,我觉得是单纯的使用 new 在堆中分配内存致使程序运行效率低,后来确实经过预分配对象内存解决了部分问题,可是还有一些会影响程序运行速度的问题没有解决,也没有深究,今天从新编写代码的时候,为了分配数据到 4 幅图片上(分别是 RGB 通道、R 通道、G 通道、B 通道),发现运行速度仍是不够,影响运行速度的缘由有几个:数组
接下来看看这几个致使程序运行速度不够的缘由:缓存
在 QtCreator 中运行程序,若是是以 Debug 模式运行的话,速度是要比 Release 模式低一些的。之前编写 Qt 程序,数据量通常不大,对于性能都没有要求,即便程序代码不够优化,但在用户使用过程当中通常不会感觉到运行卡顿,因此一直都没发现 Debug 模式和 Release 模式的性能有差别。多线程
不过其实也能猜到性能有差别的大概缘由:Debug 模式下会在最终生成的代码里面插入不少额外的代码用于调试,可是 Release 生成的代码是不会插入这些调试用的代码的,最明显的差别就是 Debug 模式生成的可执行文件比 Release 模式生成的可执行文件要大得多。app
Debug 模式下运行程序,实际 FPS 和指望的 FPS 有 6 帧的差距,差距产生的缘由是处理速度不够,致使最终生成图片的速度慢了。socket
Release 模式下即便是原始数据包的指望 FPS 到了 77 帧,实际的 FPS 也能够达到 77 帧,也就是说在处理过程当中没有出现处理速度跟不上接受数据的速度。函数
当咱们使用 Qt 程序的时候,常常会在主函数 main 里写出相似下面这样的代码:性能
int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow mw; mw.show(); return a.exec(); }
这样咱们的程序应用的生命周期就是 QApplication 所定义的,当咱们使用 QObject::moveToThread 方法将某个 QObject (子类)对象移到其余的子线程中的时候,子线程也有独立于主线程的相应的事件派发机制。优化
QObject 的多线程使用方法很巧妙,利用信号&槽机制或者是 QMetaObject::invokeMethod 方法就可让要执行的耗时函数在子线程中执行,可是若是是直接调用耗时函数,那么就会在当前的线程中执行耗时操做,致使线程阻塞。
在线程之间传递数据,若是是用信号&槽的机制,那么可能你都不须要考虑线程间的数据同步问题,但信号&槽机制是要依靠 Qt 的事件循环机制的,若是事件不能正常分发(dispatch),那么子线程中的槽函数就不会被调用。
关于 Qt 的事件循环机制和线程机制,推荐看一看官方 wiki,《线程、事件与QObject》 或者也有对应的英文原文 Threads Events QObjects。
若是频繁的调用信号,在 Qt 的事件循环中,由于前一次耗时的任务没有完成,致使对应的槽函数没法执行,最终致使处理速度跟不上。
所以对于实时性要求高的程序,Qt 的事件循环机制可能不会是你的首选,你更有可能去作的是在 Qt 的一个子线程中运行循环代码,忽略掉该子线程中的事件循环以提升程序的性能。
在接收到原始字节数据以后,最重要也是最麻烦的就是解析数据。包括识别自定义协议数据的头部信息,将数据包中的图像数据复制到缓冲区,并将缓冲区中的数据以图片的形式显示出来。接下来分享几个高效处理数据的几个小技巧:
QByteArray data; data.resize(PacketSize); // PacketSize 是预先定义好的数据字节数 // 能够简单的认为 rawData 就是从 UDP 端口中接收到的数据, // ValidDataSize 也是预先定义好的数据字段的长度 memcpy(rawData.data(), data.data(), ValidDataSize); // 上面的代码要比直接使用赋值操做 = 高效 data = rawData;
LineDataObj
(用于表示图片的一行数据)的时候,用 QMap 存储行号和指针,利用 QMap 的查找功能减小了查找或排序的时间,可是缺点是 QMap 会随着其内部的数据量增大变得缓慢,若是只须要缓存数据,建议直接使用数组存取,这样的运行效率最高。// 在类中声明一个 map QMap<int line, LineDataObj *> map; // 在方法体中使用 map 查找是否有对应的行数据 if(map.contains(line)) { // 若是有对应的行数据对象,直接将数据写入到行对象数据上 ... } else { // 若是没有,则插入一条记录 map.insert(line, lineDataObj); } // 处理完一行数据后,能够将该行数据从 map 中移除掉 map.remove(line);
能够发现,map 就是用来判断是否有对应行数据对象,而后处理结束后移除保存的行号,这并无达到缓存数据的目的,反而再插入和移除的过程当中浪费了过多时间。但若是用一个数组当作缓存区就会快不少,由于咱们减小了查找和移除记录的时间:
QVector<LineDataObj *> linePool; // LinePoolSize 是预约义的池大小 linePool.reserve(LinePoolSize); for(int i = 0; i < LinePoolSize; i++) { linePool.append(new LineDataObj()); } // 数组大小是有限的,行号倒是不断增长的,所以要设置一个起始行,保证在长时间执行程序后不会出现数组越界的问题 int diffLine = line - startLine; // 进行处理 linePool[line].setData(...);
少便是多
的原则确实给了我很大的启发)。我以前编写程序时,除了有一个 LineDataObj
用来表示行对象,还有一个 RawDataObj
表示原始的数据包对象。处理的流程多:1. 接受原始数据包 => 2. 将数据包填充到 RawDataObj 中并解析数据包的行号,RGB 类型 => 3. 根据 RawDataObj 的属性肯定对应的 LineDataObj => 4. 当 LineDataObj 存储到必定数目时生成图像。
这个流程很直观也很容易想到,可是 RawDataObj 这个数据对象其实不必使用,由于它增长了一次没必要要的内存数据复制。这彻底能够给 LineDataObj 类增长几个静态方法,判断出数据包的行号和 RGB 类型,而后将数据部分写入到 LineDataObj 的数据字段中。这样作不只能够减小内存读写的次数,并且能够在一个对象中申请大段内存,保存整行的数据,最后写入到图片时,只用将这个区域赋值到图片中便可。
最后分享一下如何在 Qt 中高效的显示图片。通常用 Qt 显示图片能够用 QLabel:
QLabel label; QImage image; // 执行一些读取图片的操做,再显示在 QLabel 上 label.setPixmap(QPixmap::fromImage(image));
可是用 QPixmap::fromImage 会从 image 的内存区域中复制一份数据到 Pixmap 中,这样的操做并不高效。咱们可使用 QImage::scanLine 方法获取它对应的内存区域,直接对内存进行操做,显示的时候不用 QPixmap::fromImage,咱们要直接将内存中的修改显示到界面中,这样咱们要定义一个类(不妨让它继承 QLabel
),重写 paintEvent 方法:
void PictureImage::paintEvent(QPaintEvent *event) { Q_UNUSED(event); if(m_index == uchar(-1)) { return; } // this->painter.drawImage(target, *m_image); QPainter p(this); // target 在构造函数中定义: // target = QRectF(0.0, 0.0, PictureImage::ImageWidth, PictureImage::ImageHeight); p.drawImage(target, *m_images[m_index]); }
像 p.drawImage(target, image)
这样就能够将图片更新到界面中,而且它会被 QPixmap 的 fromImage 方法要高效。
以前说过,模拟数据是用 Python 代码编写的,这个代码发送模拟数据的效率能够高达 100M/s,下面的截图是我在本身的笔记本(i5 8200U@1.8G)上运行的结果:
可是令我感到特别奇怪的是,有一段时间一样的代码在个人 amd ryzen 1500x@3.5G 台式机上只能达到 50M/s 的速度。我一度怀疑是英特尔和 AMD 的处理器单核性能有差别,但按道理不该该有这么大的速度差别。并且最近几天它又在个人台式机上可以跑到 100M/s 的速度。