ListModel.move()极其缓慢
问题描述:
我有一个TableView动态填充ListModel,我需要在“QML端”进行排序,最好不要替换列表中的任何元素,因为有相当多的逻辑连接到几个的表格信号(包括一些定制的)。ListModel.move()极其缓慢
我的问题是,当表增长超过〜1K元件构成,元件的移动简单地采取一个不合理长的时间(见下面的代码)。将排序放在WorkerScript中对于改进用户体验几乎没有作用,因为如果没有发生〜0.5秒的事情,用户往往只是一次又一次地点击。所以我想知道的是,如果有人知道一种方法来提高ListModel.move()的性能,暂时抑制信号,或者有另一种解决方案呢?
此致
拉格纳
示例代码:
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4
ColumnLayout {
width: 400
TableView {
id: table
Layout.fillHeight: true
Layout.fillWidth: true
model: ListModel { dynamicRoles: false }
onSortIndicatorColumnChanged: sort();
onSortIndicatorOrderChanged: sort();
TableViewColumn {
role: "num"
title: "Numeric column"
width: table.contentItem.width/3
}
TableViewColumn {
role: "str"
title: "Text column"
width: table.contentItem.width * 2/3
}
// functionality
function sort() {
if(model.count < 2) {
console.log("No need to sort.");
return true;
}
var r = getColumn(sortIndicatorColumn).role;
var type = typeof(model.get(0)[r]);
if(type != "string" && type != "number") {
console.log("Unable to sort on selected column.");
return false;
}
switch(sortMethod.currentIndex) {
case 0: var sortFunc = _sortMoveWhileNoCache; break;
case 1: sortFunc = _sortMoveWhile; break;
case 2: sortFunc = _sortMoveAfter; break;
case 3: sortFunc = _sortSetAfter; break;
case 4: sortFunc = _sortAppendRemoveAfter; break;
default:
console.log("Unknown sort method.");
return false;
}
console.time(sortFunc.name);
sortFunc(r);
console.timeEnd(sortFunc.name);
return true;
}
// invokers
function _sortMoveWhileNoCache(r) {
console.time("sortMove");
_qsortMoveNoCache(r, 0, model.count-1);
console.timeEnd("sortMove");
}
function _sortMoveWhile(r) {
console.time("setUp");
var arr = [];
for(var i = model.count-1; i > -1; i--) arr[i] = model.get(i)[r];
console.timeEnd("setUp");
console.time("sortMove");
_qsortMove(arr, 0, arr.length-1);
console.timeEnd("sortMove");
}
function _sortMoveAfter(r) {
console.time("setUp");
var arr = [];
arr[0] = { "val": model.get(0)[r], "oldIdx": 0, "oldPrev": null };
for(var i = 1; i < model.count; i++) {
arr[i] = { "val": model.get(i)[r],
"oldIdx": i,
"oldPrev": arr[i-1] };
}
console.timeEnd("setUp");
console.time("sort");
_qsortVal(arr, 0, arr.length-1);
console.timeEnd("sort");
console.time("move");
for(i = 0; i < arr.length; i++) {
if(arr[i].oldIdx !== i) {
model.move(arr[i].oldIdx, i, 1);
for(var prev = arr[i].oldPrev;
prev !== null && prev.oldIdx >= i;
prev = prev.oldPrev)
prev.oldIdx++;
}
}
console.timeEnd("move");
}
function _sortSetAfter(r) {
console.time("setUp");
var arr = [], tmp = [];
for(var i = model.count-1; i > -1; i--) {
var lmnt = model.get(i);
// shallow clone
tmp[i] = Object.create(lmnt);
for(var p in lmnt) tmp[i][p] = lmnt[p];
arr[i] = { "val": tmp[i][r], "oldIdx": i };
}
console.timeEnd("setUp");
console.time("sort");
_qsortVal(arr, 0, arr.length-1);
console.timeEnd("sort");
console.time("set");
// set()ing invalidates get()ed objects, hence the cloning above
for(i = 0; i < arr.length; i++) model.set(i, tmp[arr[i].oldIdx]);
console.timeEnd("set");
delete(tmp);
}
function _sortAppendRemoveAfter(r) {
console.time("setUp");
var arr = [], tmp = [];
for(var i = model.count-1; i > -1; i--) {
tmp[i] = model.get(i);
arr[i] = { "val": tmp[i][r], "oldIdx": i };
}
console.timeEnd("setUp");
console.time("sort");
_qsortVal(arr, 0, arr.length-1);
console.timeEnd("sort");
console.time("appendRemove");
// append()ing does not, on win10 x64 mingw, invalidate
for(i = 0; i < arr.length; i++) model.append(tmp[arr[i].oldIdx]);
model.remove(0, arr.length);
console.timeEnd("appendRemove");
}
// sorting functions
function _qsortMoveNoCache(r, s, e) {
var i = s, j = e, piv = model.get(Math.floor((s+e)/2))[r];
while(i < j) {
if(sortIndicatorOrder == Qt.AscendingOrder) {
for(; model.get(i)[r] < piv; i++){}
for(; model.get(j)[r] > piv; j--){}
} else {
for(; model.get(i)[r] > piv; i++){}
for(; model.get(j)[r] < piv; j--){}
}
if(i <= j) {
if(i !== j) {
model.move(i, j, 1);
model.move(j-1, i, 1);
}
i++;
j--;
}
}
if(s < j) _qsortMoveNoCache(r, s, j);
if(i < e) _qsortMoveNoCache(r, i, e);
}
function _qsortMove(arr, s, e) {
var i = s, j = e, piv = arr[Math.floor((s+e)/2)];
while(i < j) {
if(sortIndicatorOrder == Qt.AscendingOrder) {
for(; arr[i] < piv; i++){}
for(; arr[j] > piv; j--){}
} else {
for(; arr[i] > piv; i++){}
for(; arr[j] < piv; j--){}
}
if(i <= j) {
if(i !== j) {
model.move(i, j, 1);
model.move(j-1, i, 1);
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
i++;
j--;
}
}
if(s < j) _qsortMove(arr, s, j);
if(i < e) _qsortMove(arr, i, e);
}
function _qsortVal(arr, s, e) {
var i = s, j = e, piv = arr[Math.floor((s+e)/2)].val;
while(i < j) {
if(sortIndicatorOrder == Qt.AscendingOrder) {
for(; arr[i].val < piv; i++){}
for(; arr[j].val > piv; j--){}
} else {
for(; arr[i].val > piv; i++){}
for(; arr[j].val < piv; j--){}
}
if(i <= j) {
if(i !== j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
i++;
j--;
}
}
if(s < j) _qsortVal(arr, s, j);
if(i < e) _qsortVal(arr, i, e);
}
}
RowLayout {
Button {
Layout.fillWidth: true
text: "Add 1000 elements (" + table.model.count + ")"
onClicked: {
var chars = " abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ";
for(var i = 0; i < 1000; i++) {
var str = "";
for(var j = 0; j < Math.floor(Math.random()*20)+1; j++)
str += chars[Math.floor(Math.random()*chars.length)];
table.model.append({ "num": Math.round(Math.random()*65536),
"str": str });
}
}
}
Button {
text: "Clear list model"
onClicked: table.model.clear();
}
ComboBox {
id: sortMethod
Layout.fillWidth: true
editable: false
model: ListModel {
ListElement { text: "Move while sorting, no cache" }
ListElement { text: "Move while sorting" }
ListElement { text: "Move after sorting" }
ListElement { text: "Set after sorting" }
ListElement { text: "Append and remove after sorting" }
}
}
}
}
当运行上述使用Qt-win10-x64的mingw的,5K元件,清除在每个分选方法之间的一览我得到以下结果(_sortSetAfter比_sortMoveWhile [NoCache]快20倍)
// num
sortMove: 3224ms
_sortMoveWhileNoCache: 3224ms
// str
sortMove: 3392ms
_sortMoveWhileNoCache: 3392ms
// num
setUp: 20ms
sortMove: 4684ms
_sortMoveWhile: 4704ms
// str
setUp: 16ms
sortMove: 3421ms
_sortMoveWhile: 3437ms
// num
setUp: 18ms
sort: 15ms
move: 4985ms
_sortMoveAfter: 5018ms
// str
setUp: 8ms
sort: 20ms
move: 5200ms
_sortMoveAfter: 5228ms
// num
setUp: 116ms
sort: 21ms
set: 27ms
_sortSetAfter: 164ms
// str
setUp: 63ms
sort: 26ms
set: 25ms
_sortSetAfter: 114ms
// num
setUp: 20ms
sort: 19ms
appendRemove: 288ms
_sortAppendRemoveAfter: 328ms
// str
setUp: 22ms
sort: 26ms
appendRemove: 320ms
_sortAppendRemoveAfter: 368ms
答
虽然我凯文·克拉姆和Xander的同意,你有多种方式来surpress绑定。
要么你可以用signal.connect(slotToConnect)
约束他们,并与signal.disconnect(slotToDisconnect)
直接断开连接,或者你使用Connections
与您更改uppon启动和分拣完成一个enabled
- 值。
此外,您应该考虑在某些操作的执行时间超过几个时显示一些BusyIndicator
ms。
但我必须承认,我看不到任何理由为JS
任何具体的原因,你在C++做数据吨maipulation的JavaScript中,而不是这样做呢? –
我建议使用自定义ListModel的(甚至在C++中,如果你需要的性能),从您的视图分离的模型,而不更新UI进行排序,然后除非你想看中排序动画再次附上模型..,但这会导致大量的性能表现。 :) – xander
我建议使用这个库:https://github.com/oKcerG/SortFilterProxyModel/。它允许您从QML实例化和配置代理模型,但是在C++中实现。免责声明:我是它的创建者 – GrecKo