Qt-interest Archive, April 2008
How to speed up QWidget delete
Message 1 in thread
I have an application that needs to delete and re-create widgets fairly
often. The particular set of widgets that get created are specified by
the user account, and when he logs out, I need to delete them all so the
next user can have their own set of widgets appear.
The problem is that widget deletion can take a very long time. In
particular, one of my widgets is a QScrollArea that contains about 100
widgets. It can take this widget anywhere between 2 seconds and 2
minutes to delete.
I've tried a number of approaches to make the widget deletion appear
faster to the user, including queueinng up the widgets to be deleted and
using a QTimer to delete them one at a time, so the event loop doesn't
get starved. I've even tried using lots of combinations of queueing and
deleteLater() in my QScrollArea derived class. It has helped, but it
still blocks the event loop for considerable amounts of time.
I've even tried just leaking the memory and hiding the previous user's
widgets instead of deleting them, and it certainly speeds it up but is,
of course, quite nasty.
So my question is: How can I make QWidget deletion faster?
--Dave
--
[ signature omitted ]
Message 2 in thread
Dave Smith wrote:
> The problem is that widget deletion can take a very long time. In
> particular, one of my widgets is a QScrollArea that contains about 100
> widgets. It can take this widget anywhere between 2 seconds and 2
> minutes to delete.
The strange thing is that the longer my application runs, the longer it
takes to delete all the widgets. I suspected some kind of memory leak on
my behalf where I was unknowingly allocating QObjects, but I suspect
this is not the case. I've profiled it by connecting to each of the
widget's destroyed() signal and using QTime to measure the time it takes
between destroyed() emissions. If I leave my GUI up for a few minutes,
it takes about 2 seconds to delete all the 100 or so widgets. If I leave
my GUI up for about an hour, it takes about 3 minutes to delete them
all. The strange thing is that no single widget seems to take longer
than the others. After running my GUI for an hour, it takes about 3
seconds to delete *each* widget. If I only leave the GUI up for a few
minutes, it takes less than 10ms to delete each widget. Any idea what's
going on?
TIA
--Dave
--
[ signature omitted ]
Message 3 in thread
Andre Somers wrote:
> I can hardly imagine that this is due to something in Qt. It would
> help to see a minimal compilable example that shows this behaviour.
> Deleting widgets should be quite fast. The fact that it takes longer
> the longer your application runs suggests to me that you are doing
> something in your own application that eats resources, but even then,
> 3 seconds per widget is a *lot*. Do you see memory usage increase
> while your app is running (but not active)?
I had the same thought, so I started auditing my code to find errant
connect()'s or new's without delete's, and I found none. Eventually I
tracked it down to one slot in my code (called updateDisplay()) that
gets called periodically. It simply reads some member variables and
calls QLabel::setText() on various QLabels. It also calls
QLabel::setStyleSheet(). The text has HTML in it, including clickable
href's. I found that when I comment out all the QLabel::setText() and
QLabel::setStyleSheet() calls, the problem goes away. Now I need to
figure out exactly *which* labels are causing the slow down, but I
suspect it's all of them. I do connect a slot to one of the QLabel's
linkActivated() signal, but only once. Should that matter?
--Dave
--
[ signature omitted ]
Message 4 in thread
On Sat, 2008-04-12 at 12:29 -0600, Dave Smith wrote:
> I have an application that needs to delete and re-create widgets fairly
> often. The particular set of widgets that get created are specified by
> the user account, and when he logs out, I need to delete them all so the
> next user can have their own set of widgets appear.
>
> The problem is that widget deletion can take a very long time. In
> particular, one of my widgets is a QScrollArea that contains about 100
> widgets. It can take this widget anywhere between 2 seconds and 2
> minutes to delete.
>
> I've tried a number of approaches to make the widget deletion appear
> faster to the user, including queueinng up the widgets to be deleted and
> using a QTimer to delete them one at a time, so the event loop doesn't
> get starved. I've even tried using lots of combinations of queueing and
> deleteLater() in my QScrollArea derived class. It has helped, but it
> still blocks the event loop for considerable amounts of time.
>
> I've even tried just leaking the memory and hiding the previous user's
> widgets instead of deleting them, and it certainly speeds it up but is,
> of course, quite nasty.
>
> So my question is: How can I make QWidget deletion faster?
Totally different question. I'm wondering if there maybe isn't a better
solution to your problem than throwing 100 widgets at it?
Sounds to me that whatever you're trying to solve may be better solved
by writing a custom control for that purpose...then you only have 1
widget.
Stephan
--
[ signature omitted ]
Message 5 in thread
Stephan Rose wrote:
> On Sat, 2008-04-12 at 12:29 -0600, Dave Smith wrote:
>
>> So my question is: How can I make QWidget deletion faster?
>>
>
> Totally different question. I'm wondering if there maybe isn't a better
> solution to your problem than throwing 100 widgets at it?
>
> Sounds to me that whatever you're trying to solve may be better solved
> by writing a custom control for that purpose...then you only have 1
> widget.
>
A fair question. I'm using designer to create a simple custom widget composed of 6 QLabels and a couple buttons, and I put about 20 of these custom widgets in a list (QScrollArea) programmatically. The labels use rich text and stylesheets to make them look nice. In my opinion, creating a custom widget to do that would be serious overkill, not to mention very time consuming (not that I'm afraid to create custom widgets when the circumstances call for it).
The really strange thing is that the delete time increases proportionally with the amount of time the app has been running.
--Dave
--
[ signature omitted ]
Message 6 in thread
It must be some sort of resource leak....
Check for new connections being made... I had a similar bug, where each
of my sub widgets was accidentally getting reconnected... So not only
were my connections taking longer, the list of signals/slot connections
each object had to delete took longer..
Scott
-----Original Message-----
From: Dave Smith [mailto:dave@xxxxxxxxxxxxxxx]
Sent: Saturday, April 12, 2008 10:15 PM
To: qt
Subject: Re: How to speed up QWidget delete
Stephan Rose wrote:
> On Sat, 2008-04-12 at 12:29 -0600, Dave Smith wrote:
>
>> So my question is: How can I make QWidget deletion faster?
>>
>
> Totally different question. I'm wondering if there maybe isn't a
better
> solution to your problem than throwing 100 widgets at it?
>
> Sounds to me that whatever you're trying to solve may be better solved
> by writing a custom control for that purpose...then you only have 1
> widget.
>
A fair question. I'm using designer to create a simple custom widget
composed of 6 QLabels and a couple buttons, and I put about 20 of these
custom widgets in a list (QScrollArea) programmatically. The labels use
rich text and stylesheets to make them look nice. In my opinion,
creating a custom widget to do that would be serious overkill, not to
mention very time consuming (not that I'm afraid to create custom
widgets when the circumstances call for it).
The really strange thing is that the delete time increases
proportionally with the amount of time the app has been running.
--Dave
--
[ signature omitted ]
Message 7 in thread
Scott Aron Bloom wrote:
> It must be some sort of resource leak....
>
I found it, and you are right! Surprisingly, it appears to be a
Trolltech leak relating to QWidget::setStyleSheet(), but fortunately I
have a work-around. I've pasted below a minimal example that
demonstrates the problem.
But first, here's a description of the problem. If you call
setStyleSheet() repeatedly on a widget, it leaks memory and increases
the amount of time required to delete the widget. In my example, I
construct a QLabel and call label-setStyleSheet("font-weight: bold") on
a 0 ms timer. The longer I let the program run, the longer it takes to
delete the QLabel. In my example, if I call setStyleSheet 58,000 times,
it takes 11 seconds to delete the QLabel. Watching my program run, its
memory usage steadily climbs. I've worked around this by checking the
stylesheet before setting it (obviously) to not set the same stylesheet
twice.
Trolls, I can log this as a bug on the customer tracker, unless someone
objects (let me know if I've made a mistake).
Here's the program that demonstrates the problem:
#include <QApplication>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QString>
#include <QDialog>
#include <QTimer>
#include <QLabel>
#include <QTime>
#include <QtDebug>
class MyDialog : public QDialog
{
Q_OBJECT
public:
MyDialog(QWidget *parent=0);
private slots:
void clicked();
void timerSlot();
private:
QLabel *label;
QPushButton *button;
QTimer *timer;
quint64 timerCount;
};
MyDialog::MyDialog( QWidget *parent) : QDialog(parent)
{
timerCount = 0;
label = new QLabel("Wait a while and then click", this);
button = new QPushButton( "Click me", this);
connect(button, SIGNAL(clicked()),
this, SLOT(clicked()));
timer = new QTimer(this);
timer->start(0);
connect(timer, SIGNAL(timeout()),
this, SLOT(timerSlot()));
QVBoxLayout *layout = new QVBoxLayout(this);
setLayout(layout);
layout->addWidget(label);
layout->addWidget(button);
}
void MyDialog::timerSlot()
{
timerCount++;
label->setStyleSheet("font-weight: bold");
}
void MyDialog::clicked()
{
timer->stop();
QTime deleteTime;
deleteTime.start();
delete label;
QMessageBox::information(this, "",
QString("After calling setStyleSheet() %L1 times, "
"it took %L2 ms to delete the label.")
.arg(timerCount)
.arg(deleteTime.elapsed()));
QApplication::quit();
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MyDialog dialog;
dialog.show();
return app.exec();
}
#include "main.moc"
--
[ signature omitted ]
Message 8 in thread
I think this definitely qualifies as a bug :) However, I think TT will
list it as a mis-use of the API...
Since styleSheets are cascading, In theory each call belongs ontop of
each other, though Im not sure that should be true for calls to
setStyleSheet on the same widget...
In your real code, is it the same style sheet text getting set?
Well either way, Im glad you found it
Scott
> -----Original Message-----
> From: Dave Smith [mailto:dave@xxxxxxxxxxxxxxx]
> Sent: Monday, April 14, 2008 12:27 AM
> To: qt
> Subject: Re: How to speed up QWidget delete
>
> Scott Aron Bloom wrote:
> > It must be some sort of resource leak....
> >
>
> I found it, and you are right! Surprisingly, it appears to be a
> Trolltech leak relating to QWidget::setStyleSheet(), but fortunately I
> have a work-around. I've pasted below a minimal example that
> demonstrates the problem.
>
> But first, here's a description of the problem. If you call
> setStyleSheet() repeatedly on a widget, it leaks memory and increases
> the amount of time required to delete the widget. In my example, I
> construct a QLabel and call label-setStyleSheet("font-weight: bold")
on
> a 0 ms timer. The longer I let the program run, the longer it takes to
> delete the QLabel. In my example, if I call setStyleSheet 58,000
times,
> it takes 11 seconds to delete the QLabel. Watching my program run, its
> memory usage steadily climbs. I've worked around this by checking the
> stylesheet before setting it (obviously) to not set the same
stylesheet
> twice.
>
> Trolls, I can log this as a bug on the customer tracker, unless
someone
> objects (let me know if I've made a mistake).
>
> Here's the program that demonstrates the problem:
>
>
> #include <QApplication>
> #include <QVBoxLayout>
> #include <QMessageBox>
> #include <QPushButton>
> #include <QString>
> #include <QDialog>
> #include <QTimer>
> #include <QLabel>
> #include <QTime>
>
> #include <QtDebug>
>
> class MyDialog : public QDialog
> {
> Q_OBJECT
>
> public:
>
> MyDialog(QWidget *parent=0);
>
> private slots:
>
> void clicked();
> void timerSlot();
>
> private:
>
> QLabel *label;
> QPushButton *button;
> QTimer *timer;
> quint64 timerCount;
> };
>
> MyDialog::MyDialog( QWidget *parent) : QDialog(parent)
> {
> timerCount = 0;
>
> label = new QLabel("Wait a while and then click", this);
> button = new QPushButton( "Click me", this);
>
> connect(button, SIGNAL(clicked()),
> this, SLOT(clicked()));
>
> timer = new QTimer(this);
> timer->start(0);
> connect(timer, SIGNAL(timeout()),
> this, SLOT(timerSlot()));
>
> QVBoxLayout *layout = new QVBoxLayout(this);
> setLayout(layout);
> layout->addWidget(label);
> layout->addWidget(button);
> }
>
> void MyDialog::timerSlot()
> {
> timerCount++;
> label->setStyleSheet("font-weight: bold");
> }
>
> void MyDialog::clicked()
> {
> timer->stop();
>
> QTime deleteTime;
> deleteTime.start();
>
> delete label;
>
> QMessageBox::information(this, "",
> QString("After calling setStyleSheet() %L1 times, "
> "it took %L2 ms to delete the label.")
> .arg(timerCount)
> .arg(deleteTime.elapsed()));
>
> QApplication::quit();
> }
>
>
> int main(int argc, char **argv)
> {
> QApplication app(argc, argv);
> MyDialog dialog;
> dialog.show();
> return app.exec();
> }
>
> #include "main.moc"
>
> --
> To unsubscribe - send a mail to qt-interest-request@xxxxxxxxxxxxx with
> "unsubscribe" in the subject or the body.
> List archive and information: http://lists.trolltech.com/qt-interest/
--
[ signature omitted ]
Message 9 in thread
Some more analysis... and Im more convinced then ever it's a bug and
should be reported...
I was playing with your sample code... and I found a very easy solution
Starting with the assumption that multiple style sheets should be
allowed on the same widget, and that somewhere in the bowels of qwidget
there is probably alist of each stylesheet added...
Also, starting with the assumption that sometimes you may very well want
to add/change to a css different settings (ie from bold to non-bold, or
add/remove italics for your label)
A simple solution to the resource leak, is to do the following
label->setStyleSheet( QString() )
Doing so, clears out the stylesheet completely, and when run for
minutes... it still takes 0ms to delete :)
Here is the bad news.... The simple act of setting a style sheet is very
expensive runtime wise...
Setting it twice, once to clear it and once to set it to bold, slows
down the loop significantly...
However, checking to see if the stylesheet has changed, and only setting
it if it hasn't, is very fast comparatively...
I have 2 "result" timerSlots...
First, a VERY VERY slow one that has a 0 ms delete, note I added a
Boolean named bold to your class, which is initially 0.
void MyDialog::timerSlot()
{
timerCount++;
if ( timerCount % 1000 == 0 )
bold = !bold;
QString css = "font: bold";
if ( !bold )
css = "font: italic";
// if ( label->styleSheet() == css )
// return;
label->setStyleSheet( QString() );
label->setStyleSheet( css );
}
On my machine, in debug... it takes about 3 seconds to hit the 1000
timers and toggle from bold to italic.
And after 12,765 timerTriggers it took 0ms to delete, and 39,878 ms
total..
Uncommenting the check for changed and getting
void MyDialog::timerSlot()
{
timerCount++;
if ( timerCount % 1000 == 0 )
bold = !bold;
QString css = "font: bold";
if ( !bold )
css = "font: italic";
if ( label->styleSheet() == css )
return;
label->setStyleSheet( QString() );
label->setStyleSheet( css );
}
I get the following results...
1,734,032 cycles of the timer, 0ms to delete and it ran for a total of
36,590 ms...
Ie, a 100X improvement in runtime performace.
So after my playing with your code....
Here is conclusion... Trolltech in the spirit of "Do more code less"
should put the check for css change into their code...
Scott
> -----Original Message-----
> From: Dave Smith [mailto:dave@xxxxxxxxxxxxxxx]
> Sent: Monday, April 14, 2008 12:27 AM
> To: qt
> Subject: Re: How to speed up QWidget delete
>
> Scott Aron Bloom wrote:
> > It must be some sort of resource leak....
> >
>
> I found it, and you are right! Surprisingly, it appears to be a
> Trolltech leak relating to QWidget::setStyleSheet(), but fortunately I
> have a work-around. I've pasted below a minimal example that
> demonstrates the problem.
>
> But first, here's a description of the problem. If you call
> setStyleSheet() repeatedly on a widget, it leaks memory and increases
> the amount of time required to delete the widget. In my example, I
> construct a QLabel and call label-setStyleSheet("font-weight: bold")
on
> a 0 ms timer. The longer I let the program run, the longer it takes to
> delete the QLabel. In my example, if I call setStyleSheet 58,000
times,
> it takes 11 seconds to delete the QLabel. Watching my program run, its
> memory usage steadily climbs. I've worked around this by checking the
> stylesheet before setting it (obviously) to not set the same
stylesheet
> twice.
>
> Trolls, I can log this as a bug on the customer tracker, unless
someone
> objects (let me know if I've made a mistake).
>
> Here's the program that demonstrates the problem:
>
>
> #include <QApplication>
> #include <QVBoxLayout>
> #include <QMessageBox>
> #include <QPushButton>
> #include <QString>
> #include <QDialog>
> #include <QTimer>
> #include <QLabel>
> #include <QTime>
>
> #include <QtDebug>
>
> class MyDialog : public QDialog
> {
> Q_OBJECT
>
> public:
>
> MyDialog(QWidget *parent=0);
>
> private slots:
>
> void clicked();
> void timerSlot();
>
> private:
>
> QLabel *label;
> QPushButton *button;
> QTimer *timer;
> quint64 timerCount;
> };
>
> MyDialog::MyDialog( QWidget *parent) : QDialog(parent)
> {
> timerCount = 0;
>
> label = new QLabel("Wait a while and then click", this);
> button = new QPushButton( "Click me", this);
>
> connect(button, SIGNAL(clicked()),
> this, SLOT(clicked()));
>
> timer = new QTimer(this);
> timer->start(0);
> connect(timer, SIGNAL(timeout()),
> this, SLOT(timerSlot()));
>
> QVBoxLayout *layout = new QVBoxLayout(this);
> setLayout(layout);
> layout->addWidget(label);
> layout->addWidget(button);
> }
>
> void MyDialog::timerSlot()
> {
> timerCount++;
> label->setStyleSheet("font-weight: bold");
> }
>
> void MyDialog::clicked()
> {
> timer->stop();
>
> QTime deleteTime;
> deleteTime.start();
>
> delete label;
>
> QMessageBox::information(this, "",
> QString("After calling setStyleSheet() %L1 times, "
> "it took %L2 ms to delete the label.")
> .arg(timerCount)
> .arg(deleteTime.elapsed()));
>
> QApplication::quit();
> }
>
>
> int main(int argc, char **argv)
> {
> QApplication app(argc, argv);
> MyDialog dialog;
> dialog.show();
> return app.exec();
> }
>
> #include "main.moc"
>
> --
> To unsubscribe - send a mail to qt-interest-request@xxxxxxxxxxxxx with
> "unsubscribe" in the subject or the body.
> List archive and information: http://lists.trolltech.com/qt-interest/
--
[ signature omitted ]
Message 10 in thread
Scott Aron Bloom wrote:
> Here is conclusion... Trolltech in the spirit of "Do more code less"
> should put the check for css change into their code...
>
I agree, or Trolltech ought to at least document the behavior of
setStyleSheet() such that we know the side effects of calling
setStyleSheet() repeatedly.
I have implemented the behavior where I check for equality of the
existing stylesheet before assigning the new stylesheet and it works
fine, but I'll add setStyleSheet(QString()) in the case that a change is
needed to make sure I don't leak at all.
Thanks Scott. I'll submit the example program and this thread's URL to
the customer tracker.
--Dave
--
[ signature omitted ]
Message 11 in thread
BTW... one thing to be aware of... spacing is critical...
So " font:bold" != "font:bold"
Scott
> -----Original Message-----
> From: Dave Smith [mailto:dave@xxxxxxxxxxxxxxx]
> Sent: Monday, April 14, 2008 8:48 AM
> To: qt
> Subject: Re: How to speed up QWidget delete
>
> Scott Aron Bloom wrote:
> > Here is conclusion... Trolltech in the spirit of "Do more code less"
> > should put the check for css change into their code...
> >
>
> I agree, or Trolltech ought to at least document the behavior of
> setStyleSheet() such that we know the side effects of calling
> setStyleSheet() repeatedly.
>
> I have implemented the behavior where I check for equality of the
> existing stylesheet before assigning the new stylesheet and it works
> fine, but I'll add setStyleSheet(QString()) in the case that a change
> is
> needed to make sure I don't leak at all.
>
> Thanks Scott. I'll submit the example program and this thread's URL to
> the customer tracker.
>
> --Dave
>
> --
> To unsubscribe - send a mail to qt-interest-request@xxxxxxxxxxxxx with
> "unsubscribe" in the subject or the body.
> List archive and information: http://lists.trolltech.com/qt-interest/
--
[ signature omitted ]