如何提升 QAbstractItemModel::match 的效能



針對常用的match role, 可利用QHash (or QMap) 多存一份, 來加快 search 效率
我們在設計model通常都會定義一個存unique string value的IdRole當作model item的索引, 且會很頻繁對這個role作match, 從"flags對QAbstractItemModel::match的效能影響"中的實驗中可知,
針對string value去作match在效率上是很糟的, 所以只要model數量大到一個程度, 就會很容易使UI thread hang住. 針對這部分的參考解法大致如下,
  1. maintain一個QHash把每個model item的IdRole value跟相對應的model index存起來
  2. 複寫match, 如果是是要查IdRole, 就直接從QHash找以提升效率
  3. 由於QStandardItemModel::clear本身不會觸發rowsAboutToBeRemoved, 所以即使呼叫clear, 還是要主動去清掉hash
#include <QHash>
#include <QModelIndex>
#include <QPersistentModelIndex>
#include <QStandardItemModel>
#include <QVariant>

class MyListModel : public QStandardItemModel
{
  Q_OBJECT

public:
  enum Roles
  {
    IdRole = Qt::UserRole + 1,
    DataRole
  };
  MyListModel(QObject *parent = nullptr)
  {
    // 把每個model item的IdRole value存到QHash裡, key是id, value就是model index (必須用QPersistentModelIndex)
    // 如果是tree model記得要recursive把childrn也加進來,
    // 主要是如果使用者是先把model item的children都append好, 再append本身進來的話, 只會觸發一次rowsInserted.
    QObject::connect(this, &QAbstractItemModel::rowsInserted, [=](const QModelIndex &parent, int start, int end) {
      for (int row = start; row <= end; row++)
      {
        auto mdIndex = index(row, 0, parent);
        auto id = data(mdIndex, MyListModel::IdRole);
        m_idModelIndexHash.insert(id, QPersistentModelIndex(mdIndex));
      }
    });
    QObject::connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, [=](const QModelIndex &parent, int start, int end) {
      for (int row = start; row <= end; row++)
      {
        auto mdIndex = index(row, 0, parent);
        auto id = data(mdIndex, MyListModel::IdRole);
        m_idModelIndexHash.remove(id);
      }
    });
  }
  virtual ~MyListModel() = default;

  // 覆寫match function, 如果針對IdRole就去QHash以提升效率
  virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override
  {
    if (role != MyListModel::IdRole)
    {
      return QStandardItemModel::match(start, role, value, hits, flags);
    }

    // todo, !m_idModelIndexHash.contains just return empty can enhance performance, but m_idModelIndexHash must be 100% correct
    if (!m_idModelIndexHash.contains(value))
    {
      return QModelIndexList();
    }

    return QModelIndexList() << m_idModelIndexHash.value(value);
  }

  // 由於QStandardItemModel::clear本身不會觸發rowsAboutToBeRemoved, 所以clear記得要主動清掉hash
  void clearIdHash()
  {
    m_idModelIndexMap.clear();
  }

  QHash<int, QByteArray> roleNames() const override
  {
    QHash<int, QByteArray> hash;
    hash[MyListModel::DataRole] = "dataRole";
    hash[MyListModel::IdRole] = "idRole";
    return hash;
  }

protected:
  QHash<QVariant, QPersistentModelIndex> m_idModelIndexHash;
};


延續"flags對QAbstractItemModel::match的效能影響"中的實驗方法,
如果利用QHash得到的結果如下,
match fromelapsed (ms)
QStandardItemModel::match55194
QHash63
由此可見效率是大大大大提升的, 缺點就是你要好好管理m_idModelIndexHash, 一有差錯都會導致model行為不正確.

0 comments