在现代台式机 CPU 上(考虑到整个平台,即 RAM、总线和其他方面),每秒重新分配几次 Mb 是否有任何重大负载?
在典型的现代分配器中,一次分配的成本是固定的,并且与“小”分配的分配大小无关。对于较大的分配,分配大小为 O(N),比例常数非常低。
顶级 Qt 小部件由 QImage 缓冲区或 OpenGL 上下文(如果您使用 QOpenGLWidget)支持。窗口支持缓冲区的大小调整由 Qt 自动处理——它已经发生了,你甚至没有注意到它!这没什么大不了的,就性能而言。现代分配器并不笨,不会对堆进行分段。
在上面描述的那种平台上,当重新分配发生时,如果可能的话,它是发生在缓存中还是在 RAM 中?
这没关系,因为无论如何你都会覆盖它。当然,如果有可用的缓存线会有所帮助,并且为对象重用相同的地址会有所帮助。
Qt中处理这类问题的常用习语是什么?
有一个槽用于更新要显示的数据(例如更新图像或某些参数),并调用QWidget::update()
在paintEvent中渲染它。
其余的会自动发生。 paintEvent 需要多长时间并不重要——如果需要很长时间,UI 的响应能力将会下降,但它永远不会尝试显示过时的数据。没有事件的累积。
图像缩放通常由QImage::scaled 处理,返回一个临时图像,然后您使用QPainter::drawImage 绘制该图像。是的,那里有分配,但这些分配很快。
图像制作者的事件风暴很容易解决:当有新图像可用时,制作者会发出信号。图像消费者有一个槽,它接受图像,将其复制到内部成员,并触发更新。更新在控件返回事件循环时生效,并使用最近设置的图像。当没有其他事件要处理时,重绘将继续进行,因此无论需要多长时间:它总是会显示最新的图像。它永远不会“滞后”。
很容易验证这种行为。在下面的示例中,ImageSource 尽可能快地生成新帧(大约 1kHz)。每帧显示当前时间。 Viewer 在其 paintEvent 中休眠,将屏幕刷新率限制在 4Hz 以下:除非您在严重过热的内核上运行,否则它在现实生活中永远不会那么慢。每次屏幕刷新至少有 25 个新帧。然而,您在屏幕上看到的时间是当前时间。过期的帧会被自动丢弃。
// https://github.com/KubaO/stackoverflown/tree/master/questions/update-storm-image-40111359
#include <QtWidgets>
class ImageSource : public QObject {
Q_OBJECT
QImage m_frame{640, 480, QImage::Format_ARGB32_Premultiplied};
QBasicTimer m_timer;
double m_period{};
void timerEvent(QTimerEvent * event) override {
if (event->timerId() != m_timer.timerId()) return;
m_frame.fill(Qt::blue);
QElapsedTimer t;
t.start();
QPainter p{&m_frame};
p.setFont({"Helvetica", 48});
p.setPen(Qt::white);
p.drawText(m_frame.rect(), Qt::AlignCenter,
QStringLiteral("Hello,\nWorld!\n%1").arg(
QTime::currentTime().toString(QStringLiteral("hh:mm:ss.zzz"))));
auto const alpha = 0.001;
m_period = (1.-alpha)*m_period + alpha*(t.nsecsElapsed()*1E-9);
emit newFrame(m_frame, m_period);
}
public:
ImageSource() {
m_timer.start(0, this);
}
Q_SIGNAL void newFrame(const QImage &, double period);
};
class Viewer : public QWidget {
Q_OBJECT
double m_framePeriod;
QImage m_image;
QImage m_scaledImage;
void paintEvent(QPaintEvent *) override {
qDebug() << "Waiting events" << d_ptr->postedEvents;
QPainter p{this};
if (m_image.isNull()) return;
if (m_scaledImage.isNull() || m_scaledImage.size() != size())
m_scaledImage = m_image.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
p.drawImage(0, 0, m_scaledImage);
p.drawText(rect(), Qt::AlignTop | Qt::AlignLeft, QStringLiteral("%1 FPS").arg(1./m_framePeriod));
if (true) QThread::msleep(250);
}
public:
Q_SLOT void setImage(const QImage & image, double period) {
Q_ASSERT(QThread::currentThread() == thread());
m_image = image;
m_scaledImage = {};
m_framePeriod = period;
update();
}
};
class Thread final : public QThread { public: ~Thread() { quit(); wait(); } };
int main(int argc, char ** argv) {
QApplication app{argc, argv};
Viewer viewer;
viewer.setMinimumSize(200, 200);
ImageSource source;
Thread thread;
QObject::connect(&source, &ImageSource::newFrame, &viewer, &Viewer::setImage);
QObject::connect(&thread, &QThread::destroyed, [&]{ source.moveToThread(app.thread()); });
source.moveToThread(&thread);
thread.start();
viewer.show();
return app.exec();
}
#include "main.moc"
将图像缩放分担到 GPU 通常是有意义的。 This answer 提供了完整的解决方案。