I would like to alter the rendering schedule for updates to a CollectionView (as created by {{each}}) so that a large insertion does not block the UI thread for a long time. Ideally, I'd like to render as many elements as I can in say 50 ms, then pass control back to the UI thread and set a timeout to continue further rendering. It looks like there is some facility in Ember for implementing custom render buffer behavior, but I'm not sure where to get started with it.
Here's a jsfiddle benchmark showing insertion of 500 elements into a list blocking the UI thread for a while:
http://jsfiddle.net/Ecq8g/6/
I would like to find a better way to do this, but right now I am delaying how quickly I populate the contents of the ArrayController. Here is a really dirty example.
http://jsfiddle.net/BsjSH/1/
for (var i = 0; i <= 999; i++) {
Ember.run.later(function() {
App.ArrayController.pushObject(App.Thing.create());
}, i * 3);
}
You could improve this by only maintaining a list of content of items that would be in the viewport. I like your idea of hooking into the render function of a CollectionView's itemViewClass and only allow a certain number of views to render.
Related
I've got a Qt related question (for c++) regarding the QTreeWidget and hope someone can help.
I am working with the QTreeWidget and have run into a performance issue that I can't seem to solve. I basically have made a simple extension of it that allows me to easily filter the tree (hide items depending on filters). Running through all top level items and hiding/showing them depending on the filter is very easy, however it slows down whenever the tree is allowed to be sorted (e.g. by calling setSortingEnabled(true) on the QTreeWidget). Does anyone know a way around this or a better way to do this?
The way I filter the the items is as follows:
setUpdatesEnabled(false);
for(int i = 0; i < topLevelItemCount(); ++i)
{
// Retrieve item
const auto pItem = topLevelItem( i );
bool hide = false;
for(filter : mFilters){
// Stop if a filter already hides the item
if(hide) break;
// Check filter
hide = filter.checkFilter(pItem);
}
pItem->setHidden(false);
}
setUpdatesEnabled(true);
This function itself is very fast, it can run in one or two seconds for a test with 1 million items. However once setSortingEnabled(true) is called to also allow sorting it slows down significantly. I've checked with debugging and it's not the sorting that slows things down, the function above will now take ages to complete. It will now take a few minute at least compared to the mere seconds it took previously (when sorting is not allowed).
To me it appears that if sorting is enabled even once then the call
const auto pItem = topLevelItem( i );
or
pItem->setHidden(hide)
slows down significantly.
Can anyone shed some light on this issue or suggest a faster/better way to apply filters and/or loop through all top level items in the QTreeWidget?
I've looked online for faster ways to loop through the top level items, but most suggest topLevelItem(int i). I've not seen any thread regarding slowdown after sorting is enabled.
I've tried a few things, namely
Using QTreeWidgetIterators rather than topLevelItem(int i)
Calling setUpdatesEnabled(false) and setUpdatesEnabled(true) before and after the filter check respectively
Calling setSortingEnabled(false) and setSortingEnabled(true) before and after the filter check respectively
Retrieving all items in a QList findItems when searching for a wildcard string and looping over the returned QList
Looping over the QList<QTreeWidgetItem*> with pointers to items used when the tree was populated via addTopLevelItems(list)
None of these have had any impact on the performance unfortunately. If sorting has ever been enabled, then looping over items slows down significantly.
I am using QChart for an application. The application need to show some data realtime. There will be one chart and 24 series in the chart. The data rate is 400pts for every channel.
I used another thread for receiving and processing received data and emit the processed data to a slot for append datas to update the chart series.
I refered to https://doc.qt.io/qt-5/qtcharts-audio-example.html. In my case, each series is limited to 2000 points, if the number of points in the series is less than 2000, add the new point to the series, if the number of points in the seried is more than 2000, delete first point, move the rest of datas to left and add the new point in the end. This will make the chart look like move from right to left.
For good performance, I also used series->replace() and series->setUseOpenGL(true).
My problem is that the application will get freezed soon when it started. I tried delete the codes of update the chart, everything looked fine. Could anyone help me how to improve the performance of update the chart?
Thanks!
I have the same problem. The main problem I think is, that QLineSeries sends the signal pointAdded() and triggers a repaint. Additionally append() and remove are performance sinks. QtChart does only support QList and no form of a ring buffer as far as I know.
I tried the way putting new data into a QQueue<QPointsF> and copy the data within a timer hanler set to 20 Hz. To avoid updating I disable these:
void
MyGraph::handle_timer_timeout()
{
_chartView->setUpdatesEnabled(false);
// _chart->removeSeries(_series);
while(_buf->count()>0){
_series->append(_buf->dequeue());
_series->remove(0);
}
// _chart->addSeries(_series);
_axisX->setRange( _series->at(0).x(),
_series->at(_seriesSize-1).x());
_axisY->setRange(-1,1);
_chartView->setUpdatesEnabled(true);
}
This results in roughly 20-30% less processor usage.
I also found the hint that temporary removing the series (removeSeries(),addseries()) can result in some improvements but I can't confirm that.
This may be better but not really good enough. I hope someone finds a better solution.
or use QLineSeries::replace(). For that I use a double buffer QVector<QVector<QPointF>> *_lists:
void
MyGraph::handle_timer_timeout()
{
_chartView->setUpdatesEnabled(false);
auto listsother = (_listsCrurrent+1)%2;
auto bufcnt = _buf->count();
//
QVector<QPointF> *newData = &_lists->data()[listsother];
int idx;
for(idx=0; idx<_seriesSize-bufcnt;idx++){
newData->replace(
idx,
_lists->at(_listsCrurrent).at(idx+bufcnt));
}
for(; idx<_seriesSize;idx++){
newData->replace(
idx,
_buf->dequeue());
}
_listsCrurrent = listsother;
_series->replace(_lists->at(_listsCrurrent));
_axisX->setRange( _series->at(0).x(),
_series->at(_seriesSize-1).x());
_axisY->setRange(-1,1);
_chartView->setUpdatesEnabled(true);
}
This is more performant on my computer.
Alternatively you can take a look on QWT.
I have a custom widget which displays many items in rows:
void update(){ //this is a SLOT which is connected to a button click
QVBoxLayout *layout = this->layout();
if (layout == NULL){
layout = new QVBoxLayout;
this->setLayout(layout);
} else {
QLayout_clear(layout); //this is a function that I wrote that deletes all of the items from a layout
}
ArrayList *results = generateData(); //this generates the data that I load from
for (int i = 0; i < results->count; i++){
layout->addWidget(new subWidget(results->array[i]));
}
}
The problem is that there are about 900 items and a profile reveals that simply adding the child object to the layout takes 50% of the time (constructing takes the other 50%). Overall it takes about 3 seconds to load all of the items.
When I click on the button to load more data, the entire UI freezes for the 3 seconds and then all of the items appear together when everything is done. Is there a way to progressively load more items as they are being created?
The first trick is, as Pavel Zdenek said, to process only some of the results. You want to process as many together so that the overhead (of what we're going to do in the next step) is low, but you don't want to do anything that would make the system seem unresponsive. Based on extensive research, Jakob Nielsen says that "0.1 seconds is about the limit for having the user feel that the system is reacting instantaneously", so as a rough estimate you should cut your work into roughly 0.05 second chunks (leaving another 0.05 seconds for the system to actually react to the user's interactions).
The second trick is to use a QTimer with a timeout of 0. As the QTimer documentation says:
As a special case, a QTimer with a timeout of 0 will time out as soon
as all the events in the window system's event queue have been
processed. This can be used to do heavy work while providing a snappy
user interface.
So that means that a timer with a timeout of 0 will be executed next, unless there is something else in the event queue (for instance, a mouse click). Here's the code:
void update() {
i = 0; // warning, this is causes a bug, see below
updateChunk();
}
void updateChunk() {
const int CHUNK_THRESHOLD = /* the number of things you can do before the user notices that you're doing something */;
for (; i < results->count() && i < CHUNK_THRESHOLD; i++) {
// add widget
}
// If there's more work to do, put it in the event queue.
if (i < results->count()) {
// This isn't true recursion, because this method will return before
// it is called again.
QTimer::singleShot(0, this, SLOT(updateChunk()));
}
}
Finally, test this a little bit because there's a gotcha: now the user can interact with the system in the "middle" of your loop. For instance, the user can click the update button while you're still processing results (which in the above example means that you would reset the index to 0 and reprocess the first elements of the array). So a more robust solution would be to use a list instead of an array and pop each element off the front of the list as you process it. Then whatever adds results would just append to the list.
#Adri is generally right, the twist is that the "another thread" must be the UI thread again. The point is to allow UI thread's event loop to keep spinning. The fast and dirty way is to put QCoreApplication::processEvents() in your for() cycle. Dirty because, as the doc says, it should be called "ocassionally". It might have some overhead even if there are no UI events, and you are messing Qt's performance optimization as to when and how often spin the loop. Slightly less dirty would be to call it only ocassionally, after chunks of result.
Cleaner and proper way is to create a private slot, which pops one result element (or chunk, to speed up), adds to the layout and increments index. Then it will recall itself until end of results. The gotcha is to define connect() with forced connection type Qt::QueuedConnection, so it will get deferred after already queued UI events (if any).
And because you run in only one thread, you don't need any locking over results.
Adding example per OP's request:
While #TomPanning solution is correct, it kind of hides the real solution behind QTimer which you don't need - you don't need any timing, you just need a specific non-timer behavior upon specific parameter value. This solution does the same thing, minus the QTimer layer. On the other hand, #TomPanning has a very good point about the plain ArrayList not being very good data storage, when interaction can happen in between.
something.h
signals: void subWidgetAdded();
private slots: void addNextWidget();
ArrayList* m_results;
int m_indexPriv;
something.cpp
connect(this,SIGNAL(subWidgetAdded()),
this,SLOT(addNextWidget(),
Qt::QueuedConnection);
void addWidget() {
// additional chunking logic here as you need
layout->addWidget(new subWidget(results->array[m_indexPriv++]));
if( m_indexPriv < results->count() ) {
emit subWidgetAdded(); // NOT a recursion :-)
}
}
void update() {
// ...
m_results = generateData();
m_indexPriv = 0;
addNextWidget(); // slots are normal instance methods, call for the first time
}
The main view of my application contains a one-level (no children) QTreeView that displays on average 30,000 items. Due to the way the items are created, they are inserted into the model unsorted. This means that on application startup I have to sort the items in the view alphabetically, which takes nearly 1 second, leaving an unresponsive grey screen until it is done. (Since the window hasn't painted yet)
Is there any way I could get the sorting of a QSortFilerProxyModel into a separate thread, or are there any other alternative ways to approach this problem?
Here's my lessThan() code, for reference: (left and right are the two QModelIndexes passed to the function)
QString leftString = left.data(PackageModel::NameRole).toString();
QString rightString = right.data(PackageModel::NameRole).toString();
return leftString < rightString;
Thanks in advance.
Don't sort the items in the view. Add them to a temporary list and sort that list using QtConcurrent::run. When done (use a QFutureWatcher to know when), set up your model. While sorting is being performed, you can display a "please wait" message or a throbber.
in my application I have a few CListCtrl tables. I fill/refresh them with data from an array with a for-loop. Inside the loop I have to make some adjustments on how I display the values so data binding in any way is not possible at all.
The real problem is the time it takes to fill the table since it is redrawn row by row. If I turn the control invisible while it is filled and make it visible again when the loop is done the whole method is a lot faster!
Now I am looking for a way to stop the control from repainting until it is completely filled. Or any other way to speed things up.
Look into the method SetRedraw. Call SetRedraw(FALSE) before starting to fill the control, SetRedraw(TRUE) when finished.
I would also recommend using RAII for this:
class CFreezeRedraw
{
public:
CFreezeRedraw(CWnd & wnd) : m_Wnd(wnd) { m_Wnd.SetRedraw(FALSE); }
~CFreezeRedraw() { m_Wnd.SetRedraw(TRUE); }
private:
CWnd & m_Wnd;
};
Then use like:
CFreezeRedraw freezeRedraw(myListCtrl);
//... populate control ...
You can create an artificial block around the code where you populate the list control if you want freezeRedraw to go out of scope before the end of the function.
If you have a lot of records may be it is more appropriate to use virtual list style (LVS_OWNERDATA). You could find more information here.