ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qt图表库性能对比:Qwt、QChart与QCustomPlot实测选型指南

Qt图表库性能对比:Qwt、QChart与QCustomPlot实测选型指南 去年做一个振动监测项目设备端数据采集卡每秒钟回传几百个点需要在Qt界面上实时绘制波形还要支持缩放、游标测量和导出图片。最开始图省事直接用QWidget画曲线结果数据量一上来、窗口一放大就卡得没法看我意识到必须引入正经的图表库。当时在Qt圈里最常被提到的就是Qwt、QChart、QCustomPlot三个我干脆把三套源码都拉进工程用同一份采集数据分别做了验证最后成功选型并上线。这篇就把我实际跑过的性能数据、踩过的坑、最终的选型判断整理出来给正在做同样决策的人一个可以直接参考的结论。先说好这不是评测机构的正规模测所有数字来自我个人在 Windows 10 Qt 5.15.2 MSVC2019 64位、Release模式下的实测硬件是 i7-10700 16GB内存。不同环境下绝对数值会有出入但几个库之间的相对差距和瓶颈特征是有共性的这也是我写这篇的核心价值。1. 先说结论为什么三个库总被拿来比却根本不在一条赛道很多刚接触Qt的人以为Qwt、QChart、QCustomPlot是同一种东西选哪个纯粹看个人喜好。实际上这三个库的定位差异非常大硬要放在一起比排名反而会误导选型。我把它们的出身、维护节奏、许可证情况先说清楚后面所有性能数据和对策才有解释的依据。1.1 Qwt老牌工业级选手功能全但节奏慢Qwt的历史可以追溯到Qt 3时代由Uwe Rathmann发起项目托管在SourceForge上最初是为了弥补Qt在绘图控件上的空白。它的覆盖范围非常完整普通二维曲线、极坐标图、仪表盘、刻度尺、色阶条、甚至频谱瀑布图都有现成类。如果你做的是工业组态软件、实验室上位机这类传统桌面应用Qwt里基本能找到所有需要的控件。它的许可证是LGPLv2.1并带一个链接例外对闭源商业项目相对友好只要动态链接并且不修改Qwt源码本身一般不会把整个项目传染成GPL。这点比QCustomPlot的GPLv3要宽松也比QChart直接绑定Qt商业授权策略要容易理解。但Qwt的问题也很明显维护节奏太慢对Qt 6的适配滞后了很久。社区里积攒了不少issue没人处理文档偏老遇到问题经常要自己翻源码。另外Qwt的绘制核心偏重传统坐标轴体系的刻度计算在高频刷新场景下性能表现并不突出这一点后面实测数据会体现。1.2 QChart官方正统QML场景绕不开QChart是Qt官方在Qt 5.7开始并入的Charts模块本质上是基于QGraphicsView框架封装出的一套图表组件提供柱状图、折线图、饼图、面积图、箱线图等多种类型还支持很顺滑的缩放和平移动画效果。它最大的优势是官方血统。文档齐全和Qt Creator的集成度高尤其如果你做的是QML界面QML里的ChartView组件几乎是唯一不需要额外造轮子的官方方案。想在C后端往QML前端推数据用QSplineSeries、QLineSeries这些类直接操作就行省掉自己写一堆QObject接口的麻烦。但QChart的短板也藏在它的架构里。QGraphicsView框架带来的动画体验在小数据量下很舒服一旦数据量上去、尤其是做实时动态追加时性能会急剧恶化。还有一点需要注意Qt Charts模块在商业闭源项目里的授权策略跟随整个Qt的许可体系走如果项目不能接受GPL/LGPL条款就需要购买Qt商业授权这是团队做技术选型时必须提前协商清楚的合规成本。1.3 QCustomPlot两个文件打天下的单兵方案QCustomPlot由Emanuel Eichhammer维护整个库的核心代码就两个文件qcustomplot.h和qcustomplot.cpp。你不需要安装额外模块不需要在pro/cmake里配置一堆依赖项把这两个文件拖进工程直接编译即可运行。这一点对有严格依赖管理、或者想减少第三方库集成风险的团队非常友好。功能上它虽然没有Qwt那么全面的工业控件但曲线图、柱状图、散点图、矢量场、颜色图、以及各种Item标注都支持而且提供了非常灵活的图层layer系统。许可方面是GPLv3或商业授权二选一闭源商用需要付费购买这点和Qwt、QChart的授权策略都不一样。QCustomPlot最打动人的是性能。它的绘制管线针对性极强所有数据都存在QVector里重绘时只刷新变化区域的boundingRect实测大数据量下比QChart和Qwt都流畅不少。缺点也很真实因为是单一源代码文件首次编译比较慢功能要靠自己摸索官方文档偏简略线程安全需要自己严格把控否则崩溃率很高。2. 性能实测不同数据量下的绘制耗时与交互帧率既然标题叫性能对比我就先把实测数据摆出来。我用的数据集是模拟振动信号正弦波叠加噪声点数从1万到50万递增每次测试都先调用rescaleAxes这类接口把坐标轴范围适配到数据范围再统计从发起绘制到绘制完成的时间。2.1 测试环境和做法测试环境前面已经提了三个库都在同一个工程里分别编译成独立页面避免互相干扰。绘制时间用QElapsedTimer统计每次完整刷新后取20次的平均值。Qwt开启了抗锯齿QChart保持默认配置未启用OpenGL系列因为QChart在Qt5里的OpenGL加速本身已标记废弃QCustomPlot用默认的antialiased画线设置。为了公平三者的坐标系都设置为自动范围曲线颜色、线宽尽量保持一致。我还额外测了交互帧率。办法是数据量固定以后用鼠标反复框选缩放和平移肉眼观察界面跟手程度同时用QElapsedTimer统计每次replot/repaint的耗时。动态刷新场景则是用一个QTimer按照50ms间隔模拟采集数据到来对比三种库各自的表现。2.2 静态绘制耗时对比静态绘制的意思是数据一次性set进去之后不再变化只做整幅重绘和缩放操作。实测数据如下表数据量QwtQChartQCustomPlot1万点约12ms约9ms约3ms10万点约95ms约75ms约22ms50万点约480ms有明显卡顿感约420ms拖拽缩放困难约95ms仍能流畅交互这个结果和很多人直觉相反的地方在于QChart作为官方模块在静态大数据量下并没有比Qwt快多少甚至两者都属于能画但开始卡的状态。而QCustomPlot在50万点下还能维持接近实时刷新的交互帧率这是它底层架构设计带来的结果后面我会专门解释。2.3 实时刷新与内存走势采集类软件更关心动态追加数据的表现。我模拟了一台设备每秒上报20包、每包500个点的波形场景连续跑5分钟观察绘制频率和内存增长指标QwtQChartQCustomPlot50ms定时器触发下的刷新耗时约20-80ms波动明显约30-150ms且会累积约5-15ms稳定5分钟后内存变化约80MB基本稳定从约150MB涨到约400MB约70MB基本稳定是否适合长时间实时监控勉强可用不推荐需要专门优化很适合QChart的动态追加之所以内存会持续增长是因为QLineSeries的append操作每次都会触发series内部的dataChanged信号QChart在内部重建数据布局和坐标轴范围旧缓冲区没有被高效复用。这个问题在官方文档里没有明确警告很多人做实时波形时第一反应就是用append结果长时间运行后被内存打爆。2.4 为什么这些数字会变成这样直接给结论QCustomPlot胜在数据容器简单、部分重绘机制高效QChart败在QGraphicsView的整块重绘模型Qwt则因为坐标轴刻度计算太重高频刷新时被拖慢。从数据容器的角度说QCustomPlot把每条曲线的数据直接存在QCPGraph内部的QVectorQCPData里setData本质上是对这个Vector做批量替换然后只对图层中变化的矩形区域触发update。QChart的每个序列内部也有数据结构但QGraphicsView的场景会在数据变化时把对应图元标记为脏重绘时常常是整个plotArea一起刷新点的遍历和坐标变换被放大了好几倍。Qwt的问题则不在画线本身而在它的ScaleEngine会在每次绘制前重新计算坐标轴的刻度标签数据量越大、缩放越频繁这部分计算消耗越明显它们把这部分设计得极其精细但也因此变得很重。理解了这个机制差异你就能判断出这三个库的性能差距不是优化水平的差距而是架构路线的差距选型时不能简单按跑分定成败。3. 架构差异决定体验刷新机制、坐标系与图层系统只看跑分容易踩坑因为不同场景下瓶颈完全不一样。这一节我把三个库最核心的架构特征拆开讲包括它们的坐标系变换逻辑、刷新粒度、以及与多线程交互的方式。理解了这些你才能在设计自己的项目时避开性能陷阱。3.1 Qwt的坐标系与刻度引擎精细但昂贵Qwt把坐标轴的刻度计算、标签绘制、网格绘制完全模块化。QwtScaleEngine负责计算刻度位置QwtScaleDraw负责画刻度和标签逻辑坐标系与设备坐标系之间的变换通过QwtPlot的transform方法完成。这种设计的优点是你几乎能控制刻度的一切细节比如主刻度、次刻度数量、对齐方式、科学计数法显示等等。代价是每次重绘时如果坐标轴范围变了scaleEngine会重新计算一遍刻度序列。在涉及对数坐标、日期时间坐标时这个计算的开销更大。我做实时波形时感触特别深Qwt画50万点曲线本身耗时只有大概200ms但一旦把坐标轴设成自动缩放、数据一更新就replot刻度计算会把整体耗时推高到400ms以上。所以Qwt的优化关键不在曲线绘制而在于控制坐标轴重算频率。实际项目中我会把坐标轴范围和刻度固定住或者用一个较慢的定时器去更新坐标轴而不是每次数据到来自动rescale。另外Qwt的update和replot区别很大update只是把控件标记为需要重绘下一个事件循环才重绘replot是立即同步绘制。实时场景下优先用update而不是无脑replot。3.2 QChart的Graphics View体系动画体验好但整图重绘代价高QChart的数据展示是构建在QGraphicsView框架之上的。QChart本身是一个QGraphicsWidget序列和坐标轴都是场景里的图元。这种结构的优点是动画效果容易做得丝滑缩放、平移能产生很好的视觉反馈缺点是场景内任意一个图元变化都可能触发整个场景较大范围的更新。这里要说一个非常关键的点很多人以为关闭QChart的动画就能大幅提升性能实测确实有效但不会改变它的基本水平。我测试时把QChart::AllAnimations关闭之后10万点静态绘制从约110ms降到约75ms可一旦进入高频实时刷新它依然会出现帧率波动因为序列数据变化后QGraphicsView需要重新计算整个曲线图元的boundingRect并重绘。QChart在动态刷新时也不擅长自动管理数据范围。它不会像QCustomPlot那样数据超出范围就自动自适应而是需要你手动控制QValueAxis的range。这本来不算缺点但很多新手在实时刷新时忘了更新轴范围结果数据全部画到坐标轴范围之外看起来像曲线消失了非常迷惑。3.3 QCustomPlot的图层layer机制快就快在精准控制重绘范围QCustomPlot的架构核心是QCPLayer和QCPLayerable。每个可绘制元素曲线、坐标轴、网格、Item标注都属于某个图层每个图层维护自己的绘制区域。当数据变化引起某个图元重绘时QCustomPlot会尽量只重播该图元所在图层的脏矩形区域而不是整个控件都刷新。这种做法的直接好处是界面元素越多、布局越复杂省下来的性能越明显。比如界面上同时有曲线、游标、图例、坐标轴标签传统方案必须整块重绘QCustomPlot可能只重绘游标所在的几个pixel区域。坐标系变换方面QCustomPlot提供了coordToPixel和pixelToCoord两个接口把逻辑坐标和控件坐标互转这个在鼠标交互里极其方便。拿游标测量举例鼠标移动事件里调用pixelToCoord得到对应的数据点坐标然后更新QCPItemTracer的位置再调用replot整个流程非常直接。相比之下Qwt需要自己做坐标变换映射QChart的mapToPosition和mapToValue虽然也有但和QGraphicsView的坐标体系混在一起新手经常会混淆viewport坐标和场景坐标。多线程方面QCustomPlot自身不是线程安全的。但因为它数据结构简单只要遵循采集线程只存数据、GUI线程负责setData和replot的原则配合QTimer定时器驱动效果非常稳定。QChart因为QGraphicsView本身对线程更敏感如果你在子线程里碰了序列对象崩溃的几率和排查难度都更高。Qwt因为老派很多接口设计当初就没考虑多线程实际使用中还是要统一回到GUI线程操作。4. 避坑清单每个库都有几个绕不开的坑这一节是我最想写的部分。三个库我各踩过好几个坑有些坑能搜到答案有些坑基本得靠自己摸。我把这些经验按库整理出来每条都包含问题现象、根因和解决方案希望你能跳过这些弯路。4.1 Qwt的坑中文显示、动态刷新、坐标轴行为第一个坑是中文显示问题。Qwt的坐标轴标题、图例文本默认使用的字体列表经常不包含中文字体在Linux和部分Windows环境下显示成方框。解决方案是构造QwtText时显式指定字体例如QwtText title(电压波形); title.setFont(QFont(Microsoft YaHei, 10)); plot-setTitle(title);第二个坑是动态刷新时的数据振荡。很多人用setSamples更新曲线数据这个函数会创建一个新的内部数据数组并释放旧数组。高频实时刷新时反复创建和释放对象会带来CPU抖动表现为每隔几秒钟会有一帧明显卡顿。解决办法是提前分配一个足够大的QVectorQPointF作为缓冲区在setSamples时通过指定参数尽量让Qwt复用内部存储或者干脆把数据点数量控制在一个合理的滚动窗口内避免无限增长。第三个坑是Qwt的replot是同步全量重绘。在某些场景下Qwt会频繁触发重绘比如rescale的时候。如果不想让界面每帧都卡一定要用update()代替replot()让Qt把多次重绘请求合并到下一个事件循环。这个优化在数据量较大时收益非常明显。4.2 QChart的坑append内存膨胀、动画拖累、中文字体QChart的append内存膨胀是最大的坑前面已经用数据验证过。长时间实时监控时不要用series-append(QPointF(...))逐点追加而是用以下两种方案之一一是维护一个固定长度的QVectorQPointF作为环形缓冲区每次刷新时用series-replace(vector)二是把append改成一次性推送一批点比如每50ms把一包数据直接set到序列里。动画也是一把双刃剑。大多数模板代码都会开启QChart::SeriesAnimation或AllAnimations数据量小确实好看。一旦数据量过万动画会让交互掉帧严重。我的建议是项目开发初期就关掉动画或者只在legend、tooltip这类轻量元素上使用动画曲线本身不要开。QChart在Qt 5.15里还有一个容易忽略的点当你通过QChartView做鼠标框选缩放时默认zoom是围绕鼠标焦点进行的但如果你没有正确设置rubberBand选项会出现框选后缩放到错误区域的情况特别是和QML混合使用sceneRect时容易出问题。建议统一设置为QChartView::RectangleRubberBand并在需要时把sceneRect固定到初始区域。中文字体问题在QChart里也存在。QChart默认字体在中文Windows下往往显示正常但在嵌入式Linux环境或部分精简系统下会变成方块。保险做法是在QApplication启动时统一设置字体QApplication::setFont(QFont(Microsoft YaHei, 9));4.3 QCustomPlot的坑跨线程崩溃、编译慢、游标拖拽跨线程崩溃是QCustomPlot被问得最多的问题。现象是采集线程里直接调用plot-graph(0)-setData(...)刚开始正常跑了几分钟后程序随机崩溃崩溃位置每次都不一样可能在drawLinePlot也可能在updateLayer。根因是QCustomPlot的数据容器在绘制线程和采集线程中同时被读写两个线程抢同一个QVector导致内存损坏。正确做法是采集线程只做一件事把数据写进一个由QMutex或QReadWriteLock保护的缓冲区。GUI线程用QTimer定时取走数据并更新// 声明 QVectordouble dataBuffer; QMutex dataMutex; // 采集线程 { QMutexLocker locker(dataMutex); dataBuffer.append(value); } // GUI线程QTimer 30ms触发 { QMutexLocker locker(dataMutex); ui-customPlot-graph(0)-setData(xData, dataBuffer); ui-customPlot-replot(); }第二个坑是编译速度。qcustomplot.cpp体积巨大且模板展开严重首次编译在机械硬盘环境下可能耗时好几分钟。建议把它加入预编译头文件或者对qcustomplot.cpp单独设置较高的优化级别减少日常增量编译时的重复分析。另外QCustomPlot 2.1版本之后API有一定变化网上很多老教程基于2.0照着写经常提示找不到函数注意看包内版本号。第三个坑是游标测量和可拖拽标签。用QCPItemTracer做游标时有些人会发现曲线数据更新后游标位置不动了这是因为QCPItemTracer默认只根据graph数据变化时自动更新但如果你手动setGraphKey它不会自动触发replot。在数据刷新后记得调用replot如果要做拖拽标签给QCustomPlot安装事件过滤器在mousePress和mouseMove里用pixelToCoord把鼠标坐标转回逻辑坐标然后更新QCPItemText的position。这里有个细节拖拽后如果不调用layerable-update()可能出现拖拽痕迹残留因为Item所在图层的脏矩形没有被标记。遇到残影时手动调用customPlot-layer(overlay)-replot()即可注意overlay图层是专门给这类浮动元素用的。第四个大坑是发布部署。QCustomPlot编译进程序后发布时需要一并带上Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll以及platforms/qwindows.dll否则Release版在别的机器上直接打不开。如果机器上装了多个Qt版本还会遇到类似cannot mix incompatible Qt library的报错检查环境变量PATH里是否混入了不同版本的Qt bin目录确保所有dll都来自同一个编译套件。用windeployqt工具一键补齐依赖后再手动确认一下platforms目录存在。5. 场景化选型对照你的项目情况选库而不是选最热的到这里三个库的性能特点和坑都讲完了最后落到选型。我不打算给出谁最强的单一答案因为结合项目场景、团队能力、许可证约束后答案会完全不同。下面这张对照表可以帮助你快速定位选型维度QwtQChartQCustomPlot实时波形/高频采集中等偏低高首选静态大数据量分析中等中等高极坐标/仪表盘等工业控件强弱一般QML界面支持无原生支持无坐标轴刻度精细控制强中等中等上手难度中低低第三方依赖单独控件库Qt Charts模块两个源文件商业闭源友好度LGPL例外较友好需遵循Qt授权策略GPLv3或付费长期维护活力偏弱官方持续单人多产更新稳定如果你做的是数据采集上位机、音频波形、示波器这种对实时性要求极高、数据量动辄几十万点的桌面应用QCustomPlot基本是首选。我用它做出来的振动监测界面50万点曲线在普通办公电脑上依然能流畅拖动这个体验Qwt和QChart都做不到。如果你的项目是QML界面为主需要图表和触摸交互、动画过渡无缝配合那就不要犹豫选QChart。虽然它的动态大数据量性能不如QCustomPlot但在新式HMI、移动端风格的界面里QML端的ChartView组件省下的开发量远大于性能优化的成本。通过预分配点数和replace方式管理序列数据QChart在大多数数据量不超过十万的UI场景下完全够用。如果项目有大量工业仪表、极坐标雷达图、或者老工程本身就是基于Qwt开发的那就继续用Qwt。它的极坐标、色阶条、仪表控件是另外两个库替代不了的东西。团队里有人熟悉Qwt的话维护成本也远低于推到重来。最后说一个建议如果时间允许不要光看对比文章就拍板。花一天时间把三个库都接到你的真实数据流里跑一遍观察数据量、刷新频率、交互手感这三个指标你会有非常直观的感受。我自己就是把Qwt先放进去试了三天又切到QCustomPlot解决了一个性能瓶颈后才最终敲定的。工具这东西只有跑过你的真实数据才知道它是不是真的趁手。
返回列表