Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps
-
Hi guys,
I'm currently trying to check whether QT is the right pick for our real-time analytical interface. We're facing the need to be able to render a couple of numbers and plots in real-time. I've checked already the QChart widgets and decided that we will never use them as they are not made for real-time charts (CPU usage explodes when having 4 charts updating 30times per second).
However, I'm thinking now whether QT's widgets are in general a good pick for a GUI that gets tons of data to display from the server. Roughly speaking, we display a couple of dozens numbers (QLabel) and tableView in real-time (means 20-30 times per second).
I've created a simple example to show how performant QT's widgets are, and I wonder if this is the maximum I can get? I display in my example 20 QLabels and update their text 30times per second using a QTimer.
// main.cpp #include <QtWidgets/QApplication> #include <QLabel> #include <QVBoxLayout> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); QApplication a{argc, argv}; QWidget window; QVBoxLayout *layout = new QVBoxLayout(); const int labelsCount = 20; window.setLayout(layout); QVarLengthArray<QLabel, labelsCount> labels(labelsCount); QLabel display; for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1)").arg(i); auto & label = labels[i]; layout->addWidget(&label); } QTimer *timer = new QTimer(); timer->setInterval(1000/30); int counter = 0; QObject::connect(timer, &QTimer::timeout, [&] { counter++; for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1): %3").arg(i).arg(counter); labels[i].setText(text); } }); timer->start(); window.show(); return a.exec(); }
The problem I'm facing is that this already burns 25% of one CPU core (I got a Intel i7 6x 3.20GHz). This is unfortunately completely not acceptible as the GUI will have way more QLabels, QLists, etc updating in that speed. I also did a comparison to very light-weight intermedia mode GUIs like "Dear imGUI", and I could update there tons of labels in 100fps with having only 4% CPU usage (with layouting as well). in QT, even if I go down to 15 fps I get constant 15% cpu usage, just to display the stuff. There's no client-server communication, no progress-bars, charts, yet. So I'm afraid I'll end up eating one full CPU core when placing all necessary widgets on the window and start to draw in near "real-time".
So, my question is: Is this performance the status-quo of QT, or are there somewhere some knobs to turn to improve the performance? I'm afraid it has to do with the complex layout/box-model of each widget that needs to be calculated every frame, which is needed as the content of a widget can change of course and having everything static doesn't look good. I tried already with fix size of each QLabel which improved performance a bit, but still burns a lot of CPU.
-
Hi and welcome to the forums
Using widgets that way is heavy.
Normally you would use a view and delegate to do custom painting for speed reason. ( and mem use)If you need to display lots of text, then a simple subclass with paintEvent to directly display
the text from the incoming buffer would most likely speed it up alot.Also, i see you set AA_UseOpenGLES. Widgets are NOT accelerated by openGL.
So if you need that, http://doc.qt.io/qt-5/graphicsview.html
can be used.Also there is QML which might be suited.
So short answer is - huge of amount of QLabels are not good for anything real time.
-
HI Mrjj,
thanks for the quick reply, much appreciated.
@mrjj said in Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps:
Also, i see you set AA_UseOpenGLES. Widgets are NOT accelerated by openGL.
I see, so that might be the core misunderstanding of me vs QT. I expected QT to use OpenGL when available to speed up drawing. Do you know the reason QT does not use OpenGL when available and instead always uses software rendering for QWidgets? And do you know whether there's a way to draw QLabel, QPushButton in OpenGL?
So, I have basically 3 options:
- Use QWidgets where no real-time data is being displayed (dialog, buttons, selectbox, ...) and use QGraphicsScene for real-time data, which requires me to implement a LayoutManager on my own, I guess. Also as soon as I need a button right beside that real-time openGL view I have to either use QPushButton over the QGraphisScene (I guess not very enjoyable to keep the layout in sync then) or re-build the look and feel of QPushButton in QGraphicsScene on my own.
- Build everything in QGraphicsScene, so I can't use basic QWidgets like buttons, selectbox, layoutmanager anymore.
- Don't use QT and use imGui which only uses openGl and provides some kind of layout manager, buttons, selectbox, etc already.
If it would be possible to draw QWidgets like buttons, Qlabel and stuff like + using some LayoutManager (like QGridLayout) in QGraphicsScene, that would be fantastic and would solve my issue. However, I feel, reading again through your reply, that this isn't possible.
@mrjj said in Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps:
Normally you would use a view and delegate to do custom painting for speed reason. ( and mem use)
If you need to display lots of text, then a simple subclass with paintEvent to directly displayDoes this mean, I have to subclass QLabel, QButton etc and print/paint everything on my own in OpenGL? I'm not sure if I understand this approach right. Do you have by any chance a link for me, so I can read further?
If I have to print everything on my own this might be a show-stopper, as I then have almost no support from QT anymore in regards to styling the widgets. I'm also not sure how this works with those shiny layout mangers then. I'd like to re-use them in OpenGL, which I guess they are based on some base-class that provides the bounding-boxes of each widget so they can calculate the actual layout. Do you know whether I can use those layout manager in my GraphicsView?@mrjj said in Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps:
So short answer is - huge of amount of QLabels are not good for anything real time.
Thanks, that makes it very clear to me to not use QLabels or any other QWidget to display raply changing data. I thought I could use QTableView, but again, same issue - 12cols รก 10rows, changing values even for 15fps burns my CPU.
-
@marcj
Hi
QWidgets were originally designed for Desktop use and as far as i know its not possible to make them
use openGl natively, also due to the fact the forms are not openGL enabled.
Some have done it with rendering the widgets to textures and used event forwarding to make them
look live but it sounded like a mess.QGraphicsScene can use Widgets
http://doc.qt.io/qt-5/qgraphicsproxywidget.html
but im not sure about performance. ( you should test)Also QGraphicsScene has some layouts
http://doc.qt.io/qt-5/qtwidgets-graphicsview-basicgraphicslayouts-example.htmlQML and http://doc.qt.io/qt-5/qtquickcontrols-index.html were invented to make Qt better suited for mobile and accelerated devices. They are always using openGL unless you switch backend.
Also as a note. you can mix both QML and Widgets
http://doc.qt.io/qt-5/qquickwidget.htmlThere is also option to use
http://doc.qt.io/qt-5/qopenglwidget.html
so you have an accelerated area, and normal buttons next to it. -
Thanks Mrjj,
that helped me quite a lot!
I've come up with following code to use GraphicsView directly and draw some texts (hopefully in OpenGL) using QGraphicsTextItem.
// main.cpp #include <QtWidgets/QApplication> #include <QTimer> #include <QGraphicsScene> #include <QGraphicsView> #include <QtOpenGL> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); QApplication app(argc, argv); const int labelsCount = 20; QGraphicsScene scene; QGraphicsView view(&scene); //regarding documentation, this enabled OpengGL //and in fact, the rendered font looks different when having this line active view.setViewport(new QGLWidget()); QVarLengthArray<QGraphicsTextItem, labelsCount> labels(labelsCount); for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1)").arg(i); labels[i].setPlainText(text); labels[i].setPos(0, i * 15); scene.addItem(&labels[i]); } QTimer *timer = new QTimer(); timer->setInterval(1000/30); int counter = 0; QObject::connect(timer, &QTimer::timeout, [&] { counter++; for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1): %3").arg(i).arg(counter); labels[i].setPlainText(text); } }); timer->start(); view.show(); return app.exec(); }
However, I'm still at 18% CPU usage. To me, displaying 20 text item without layout manager shouldn't be anything that costs so much CPU. Do I do something wrong here? It looks to me, it uses now OpenGL (text renders different, not so sharp anymore).
-
@marcj
Hi
Could you test http://doc.qt.io/qt-5/qtwidgets-graphicsview-chip-example.html
and zoom in and out.As far as i recall you have to do
QOpenGLWidget *glWidget = new QOpenGLWidget();
setViewport(glWidget); to switch to openGl.But i have only tried the older QGLWidget so not sure it still applies.
Wait a bit and other more heavy openGL users will tell if you need something to use openGl.
I also seen many posts about it here but search-fu didnt bring it up ;) -
Here is my code using QGraphicsProxyWidget that is returned when I add QLabel into a QGraphicsView.
// main.cpp #include <QtWidgets/QApplication> #include <QTimer> #include <QGraphicsScene> #include <QGraphicsView> #include <QtOpenGL> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); QApplication app(argc, argv); const int labelsCount = 20; QGraphicsScene scene; QGraphicsView view(&scene); //regarding documentation, this enabled OpengGL //and in fact, the rendered font looks different when having this line active view.setViewport(new QGLWidget()); QVarLengthArray<QLabel, labelsCount> labels(labelsCount); for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1)").arg(i); labels[i].setText(text); labels[i].setGeometry(QRect(QPoint(0, i * 15), QSize(100, 15))); scene.addWidget(&labels[i]); } QTimer *timer = new QTimer(); timer->setInterval(1000/30); int counter = 0; QObject::connect(timer, &QTimer::timeout, [&] { counter++; for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1): %3").arg(i).arg(counter); labels[i].setText(text); } }); timer->start(); view.show(); return app.exec(); }
It is in fact OpenGL rendered now, however on my working station OSX 10.12.4 I get tons of warning messages in the console every time the text renders.
QMacCGContext:: Unsupported painter devtype type 1 QMacCGContext:: Unsupported painter devtype type 1 QMacCGContext:: Unsupported painter devtype type 1 QMacCGContext:: Unsupported painter devtype type 1
Probably related to https://bugreports.qt.io/browse/QTBUG-32639.
However, still same slow performance of over 15% CPU usage.
Running out of ideas here. :)
-
@marcj said in Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps:
Unsupported painter devtype type 1
Gah , i think you hit a bug with QGLWidget.
(its the old one, the new is QOpenGLWidget )https://bugreports.qt.io/browse/QTBUG-32639
sounds like that.Are you on mac ?
-
Btw, do you need anything besides an array of texts ?
I wondering how how fast a small custom class would be.
(to draw text)But i get the feeling you need openGL for other stuff too?
-
Yes, I'm on a Mac.
@mrjj said in Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps:
As far as i recall you have to do
QOpenGLWidget *glWidget = new QOpenGLWidget();
setViewport(glWidget); to switch to openGl.I tried both separately, the differences weren't that big.
//not having openGL here results in 18.5% usage view.setViewport(new QOpenGLWidget()); //17.1% usage view.setViewport(new QGLWidget()); //17.8% usage
The thing is, I see definitely differences in the rendered text (openGL is more blurry and more bold), however the CPU-usage is almost the same, that leads me to the conclusion that there is something somewhere else the bottleneck.
@mrjj said in Render/Layout performance very slow. High CPU usage for only 20 QLabels in 30fps:
Btw, do you need anything besides an array of texts ?
Yes, actually the main view is a TableList/Grid, with roughly 10 columns and max 20 rows.
Most columns are text, but we have also small plots in one column and in another column a progress-bar. The data for that view is coming from a streaming server that is holding machine learning data that yields real-time data in high frequency. It's important to display the stuff in at least 15fps (so plot and progress-bar is smooth). I actually thought rendering the text alone is the smallest thing to think about. :D well it turned out, I have to go to the basics first.The other first is actually more complex (both are displayed at the same time, the second when a row from the TableList is selected) which shows anything in realtime about that in more detail manner. Bigger and more plots (up to 20 at the same time), console outputs in realtime, some graphs, progress-bars, quite a lot of status text (single QLabels aligned in a grid).
Here's a screen of it:
I need OpenGL for the plots and progress-bars (Custom made), due to performance reason. Obviously :)
-
Hi
hmm, i would have imagined better performance.
I think the CPU usage comes from keeping event loop busy
when you set the text, it triggers a paint event and since it does it
very fast i assume the main event loop is always busy.Ah, ok so we cant just cheat and make a custom control that draws array of text.
You have other elements in that layout ?QLabel can have border and and be styles so compared to raw
QPainter.drawText it is a it heavy(er).
Not sure how much it would help with custom though.
Going to try with your sample and see.Update:: looks nice. very modern.
-
Wondering:
Those in Created ID etc
are the labels ? -
Just for test
Seems to use 6-8% on windows
-
The thing is, the prototype that has most of the stuff already is implemented in TypeScript/HTML5 (no OpenGL here), and we have at 15fps roughly the same CPU usage as QT has with displaying 20 QLabels at 30fps. We wanted to pump it up to 60 fps to get a very smooth running GUI, but that would have cost one CPU core using the HTML5 engine, so I gave QT a try. I'm afraid QT's CPU usage goes through the roof when I implement it using TableView and way more QLabels, Buttons, SelectBox etc as well.
So strictly speaking, I thought we could go the native approach to save a LOT cpu circles, but it seems Chromium's HTML engine is already highly optimised as we can compare directly the CPU usage here. I see however an incredible huge CPU usage difference when I compare QT/Chromium with imGUI, so I still have the hope that I can get the same performance using QT. ๐
"Create ID" It's actually a simple table grid, just text. First row has special styling due to be the table header with some onclick events.
-
Hi
Give it a day more so other timezones come online.
Some user knows far more about Qt on mac than me.Im pretty sure it can get better but i agree with you that
it doesn't look as it will scale well.I have seen reports on QWidgets and macOS having not optimal performance especially
with retina displays but im not using macOS so im not sure what current status is. -
Hi,
If you want something fancy and with OpenGL then @mrjj's recommendation of QtQuick is the way to go. QtQuick is OpenGL rendered by default.
Qt Widgets have never been hardware accelerated and likely won't be any time soon. They allow you to build applications for system with lower specs.
As the warning of QGraphicsProxyWidget states, that class should not be used in hight performance scenarios which your application is.
As for Apple's OpenGL stack, AFAIK, it's known to not be most recent or up to date regarding the standard, so beware of that when comparing to other systems.
On an unrelated note, it's Qt, QT stands for Apple QuickTime which you are likely not using even if developing on macOS.
-
Alright, I played more with QPainter, and made my own custom QWidget that uses paintEvent() to call
painter->drawText
directly. That should be the fastest I can get, right? Indeed, it was roughly twice the speed (8% vs 17%). However, there's no automatic geometric calculation yet for the layout manager.My code:
// main.cpp #include <QtWidgets/QApplication> #include <QTimer> #include <QGraphicsScene> #include <QGraphicsView> #include <QGridLayout> #include <QtOpenGL> #include <QOpenGLWidget> #include <QDebug> class MyWidget: public QWidget { public: QString text; QSize size; MyWidget() : QWidget() { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); size.setWidth(100); size.setHeight(15); } void paintEvent(QPaintEvent *event) override { QPainter painter(this); painter.setPen(Qt::black); painter.setFont(QFont("Helvetica", 14)); painter.drawText(0, 14, text); } QSize sizeHint() const { return size; } void setPlainText(QString text) { this->text = text; update(); } }; int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); QApplication app(argc, argv); const int labelsCount = 200; const int cols = 10; QGraphicsScene scene; QGridLayout *layout = new QGridLayout(); QGraphicsView view(&scene); view.setLayout(layout); //regarding documentation, this enabled OpengGL //and in fact, the rendered font looks different when having this line active view.setViewport(new QOpenGLWidget()); //helps a lot the more draws we have // view.setViewport(new QGLWidget()); //doesnt work on OSX QVarLengthArray<MyWidget, labelsCount> labels(labelsCount); for (int i =0; i < labelsCount; i++ ){ labels[i].setPlainText(QString("Hallo %1").arg(i)); layout->addWidget(&labels[i], floor(i / cols), i % cols); } QTimer *timer = new QTimer(); timer->setInterval(1000/30); int counter = 0; QObject::connect(timer, &QTimer::timeout, [&] { counter++; for (int i =0; i < labelsCount; i++ ){ auto text = QStringLiteral("(%1): %3").arg(i).arg(counter); labels[i].setPlainText(text); } }); timer->start(); view.show(); return app.exec(); }
After this good news I tested with a more realistic scenario: Having 200 labels updating in real-time (30fps). Screenshot:
At the bottom you see my HTOP Cpu utilization from that process. Unfortunately, still at 50% of a Intel i7 with over 3Ghz. The GPU isn't the bottleneck either, since I got a NVIDIA Titan Xp with 12GB RAM - no retina Display.
I'm running out of ideas here since I've never used QT until today and probably lack some fundamentals. I actually can't believe that printing directly with the QPainter is so darn slow. so there must be something I'm currently missing . Does anyone have an idea what else I could do? (except to leave QT behind)
-
I tried further to see where the bottleneck is. My next example shows how 200 very simple progress-bars perform. Result is that it renders way faster, only 13% CPU usage (30fps). However, drawing such OpenGL primitives is in other GUI abstraction an extremely easy task and costs almost zero CPU.
Code:
// main.cpp #include <QtWidgets/QApplication> #include <QTimer> #include <QGraphicsScene> #include <QGraphicsView> #include <QGridLayout> #include <QtOpenGL> #include <QOpenGLWidget> #include <QDebug> class MyWidget: public QWidget { public: QString text; int value = 0; //0 -> 100 QSize size; MyWidget() : QWidget() { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); size.setWidth(100); size.setHeight(15); } void paintEvent(QPaintEvent *event) override { QPainter painter(this); painter.fillRect(QRect(0, 0, value % 100, 14), Qt::black); } QSize sizeHint() const { return size; } void setValue(int value) { this->value = value; update(); } }; int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); QApplication app(argc, argv); const int labelsCount = 200; const int cols = 10; QGraphicsScene scene; QGridLayout *layout = new QGridLayout(); QGraphicsView view(&scene); view.setLayout(layout); //regarding documentation, this enabled OpengGL //and in fact, the rendered font looks different when having this line active view.setViewport(new QOpenGLWidget()); //helps a lot the more draws we have QVarLengthArray<MyWidget, labelsCount> labels(labelsCount); for (int i =0; i < labelsCount; i++ ){ layout->addWidget(&labels[i], floor(i / cols), i % cols); } QTimer *timer = new QTimer(); timer->setInterval(1000/30); int counter = 0; QObject::connect(timer, &QTimer::timeout, [&] { counter++; for (int i =0; i < labelsCount; i++ ){ labels[i].setValue(counter); } }); timer->start(); view.show(); return app.exec(); }
Screen:
I also just made sure it's really not my hardware and implemented in another GUI library almost the same case: 200 progress-bars (although in this example it shows also text). Result is 4% CPU usage, but with a major difference that it renders at 100fps. If I go 100 fps in Qt example above, I get 50% cpu usage. If I add text right next to the rect I get probably 100%.
Screen of the other GUI lib:
So I guess there must be something in QT where I can have more direct access to the OpenGL abstraction.