2017-02-15 137 views
0

我有一個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 
+0

任何具體的原因,你在C++做數據噸maipulation的JavaScript中,而不是這樣做呢? –

+3

我建議使用自定義ListModel的(甚至在C++中,如果你需要的性能),從您的視圖分離的模型,而不更新UI進行排序,然後除非你想看中排序動畫再次附上模型..,但這會導致大量的性能表現。 :) – xander

+2

我建議使用這個庫:https://github.com/oKcerG/SortFilterProxyModel/。它允許您從QML實例化和配置代理模型,但是在C++中實現。免責聲明:我是它的創建者 – GrecKo

回答

2

雖然我凱文·克拉姆Xander的同意,你有多種方式來surpress綁定。

要麼你可以用signal.connect(slotToConnect)約束他們,並與signal.disconnect(slotToDisconnect)直接斷開連接,或者你使用Connections與您更改uppon啓動和分揀完成一個enabled - 值。

此外,您應該考慮在某些操作的執行時間超過幾個時顯示一些BusyIndicatorms

但我必須承認,我看不到任何理由爲JS

+1

所以你可以看到一個原因? (: – GrecKo

+0

* negative concord * – derM

+0

將信號處理程序移動到Connections聽起來非常有希望,但會給出一個嘗試。至於JS部分,正如我在上面的註釋中提到的,QML前端只是其中一個,並且發送數據在後端和前端之間_may_會更費時間 – Ragnar