[旧日谈]关于Qt的刷新事件频率,以及我们在Qt的框架上做实时的绘制操作时我们该关心什么。
最近在开发的时候,发现一个依赖事件来刷新渲染的控件会导致程序很容易异常和崩溃。
当程序在运行的时候,其实软件本身的负载并不高,所以在Demo下运行一切良好(良好吗?),但是时装到实际项目下,就发现程序异常崩溃。
后面经过几轮排查,我们发现由这个控件造成的,因为控件的刷新频率太高了。
先来说说这个控件。这个控件的操作实际上依赖了Qt的moveEvent,当鼠标按下之后,然后鼠标移动会触发moveEvent,则这个控件会跟随鼠标移动。这个控件在移动了之后,一下子数据就多到把整个数据流顶爆了。
我一开始并没有想过这个问题,因为我想的是我这边渲染没问题,框架上的事情按理说就不用我关心了。但是在我的测试和开发中,我发现事情远没有这么简单。
函数计时
为什么要提到函数计时,因为这里控件的操作我需要做一个计时操作,来检测到底是哪里耗时过多。
我这里的界面并不是只管自己发布数据,而是连着很多个控件一起刷新,这就导致了一个问题。我发现其实我在其他控件上进行移动和检查花费的事件更多更频繁。
至于我是如何发现的,我这里分享一个RAII型的计时器,用于计算一个函数从开始到结束的总时间
class TimeCounter {
public:
TimeCounter(const QString& FunctionName) {
if (PublicVar::ins().blnCountingTimeMode) {
qDebug() << "Function Begin at : " << FunctionName;
this->functionName = FunctionName;
timer.start();
}
};
~TimeCounter() {
if (PublicVar::ins().blnCountingTimeMode) {
qDebug() << " Function " << this->functionName << "time used with :" << timer.elapsed() << "milliseconds.";
}
}
private:
QElapsedTimer timer;
QString functionName;
};
使用范例:
void function(){
TimeCounter timer(__FUNCTION__);
QThread::msleep(10);
}
刷新率?
既然我这个函数是依赖moveEvent来刷新界面的,也就是说我这个函数调用的频率是严格与moveEvent的刷新频率同步的。但是moveEvent的刷新率是多少?这个问题我完全没有考虑过。
显然,这个moveEvent的刷新率应该是大于60的,因为我在60hz的屏幕上完全感受不到卡顿。但那具体是多高?
moveEvent的刷新频率与鼠标的刷新率有关
是的,你没看错,moveEvent的刷新频率与鼠标的刷新率有关。在这里我可以做一个函数,来对moveEvent的刷新率进行一个简单的测算。
QtWidgetsApplication3.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication3.h"
#include "qtimer.h"
class QtWidgetsApplication3 : public QMainWindow
{
Q_OBJECT
public:
QtWidgetsApplication3(QWidget *parent = nullptr);
~QtWidgetsApplication3();
QTimer* timer;
protected:
void moveEvent(QMoveEvent* event) override;
private:
size_t m_moveEventCount = 0;
void onTimerTimeout();
Ui::QtWidgetsApplication3Class ui;
};
QtWidgetsApplication3.cpp
#include "QtWidgetsApplication3.h"
QtWidgetsApplication3::QtWidgetsApplication3(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
this->timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &QtWidgetsApplication3::onTimerTimeout);
timer->start(1000); // 每1000毫秒(1秒)触发一次
}
QtWidgetsApplication3::~QtWidgetsApplication3()
{}
#include "qdebug.h"
void QtWidgetsApplication3::moveEvent(QMoveEvent * event)
{
QMainWindow::moveEvent(event); // 确保调用基类的 moveEvent 函数
// 递增计数器
m_moveEventCount++;
}
void QtWidgetsApplication3::onTimerTimeout()
{
// 打印 moveEvent 每秒触发的次数
qDebug() << "moveEvent count per second:" << m_moveEventCount;
// 重置计数器
m_moveEventCount = 0;
}
测试结果:
1.鼠标回报率125hz
刷新率大概在126times/s
2.鼠标回报率250hz
刷新率大概在250times/s
3.鼠标回报率500hz
刷新率大概在400tick/s
4. 鼠标回报率1000hz
刷新率大概在400tick/s,最高可以到700tick/s
这个负载完全把我吓了一跳,因为我这里实际上根本用不到这么高的刷新率,对于一般的产品来说,30-60hz 的刷新率已经可以让整个产品看起来堪称流畅了,特别是这种性能关键而且和很多控件连带的地方。
结论
如果控件是依赖moveEvent来刷新界面的,那么这个控件的刷新频率就会依赖鼠标的刷新频率。如果那个地方的控件移动对后台进行的操作比较耗时,数据量比较大的时候,或者这个控件连带着很多空间一起操作的时候,可能会需要考虑到鼠标分辨率的影响。