Ускорьте фильтрацию QSortFilterProxyModel при работе с почти большими наборами данных.

Раньше я спрашивал a question о фильтрации нескольких столбцов, которую нам нужно представляют строки, соответствующие более чем одному шаблону фильтра.

Теперь при работе с большими таблицами (под big я подразумеваю около 200 000 строк и 4 столбца) фильтрация замедляется, если у нас такая большая таблица (обычно это хуже всего для первых двух символов шаблона фильтра).

Итак, что вы предлагаете по этому поводу?

Примечание. У меня есть собственная высокопроизводительная модель исходных данных (вместо QStandardItemModel), основанная на примере this. просмотреть это количество строк примерно за 1 секунду

Изменить 1

Изменение моего метода с этого:

bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
if (/* filtering is enable*/) {
    bool _res = sourceModel()->data(sourceModel()->index(source_row, 0, source_parent)).toString().contains( /*RegExp for column 0*/);
    for (int col = 0; col < columnCount(); col++) {
        _res &= sourceModel()->data(sourceModel()->index(source_row, col + 1, source_parent)).toString().contains(/*RegExp for column col + 1*/);
    }
    return _res;
}
return true;

}

К этому :

bool DataFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
    if (_enable) {
        return (sourceModel()->index(source_row, 0, source_parent.child(source_row, 0)).data().toString().contains( /*string for column 0*/ ))
            && sourceModel()->index(source_row, 1, source_parent.child(source_row, 1)).data().toString().contains(/*string for column 1*/))
            && sourceModel()->index(source_row, 2, source_parent.child(source_row, 2)).data().toString().contains(/*string for column 2*/))
            && sourceModel()->index(source_row, 3, source_parent.child(source_row, 3)).data().toString().contains(/*string for column 3*/));
    }
    return true;
}

Look работает отлично. Теперь фильтрация работает как часы без задержек


person IMAN4K    schedule 13.10.2016    source источник
comment
1. Профилируйте производительность вашей модели. 2. Не используйте регулярные выражения для фильтрации, пишите свои собственные методы. 3. Если вам действительно нужна поддержка больших данных — посмотрите на in-memory SQLite как на источник для модели и фильтрации.   -  person Dmitry Sazonov    schedule 13.10.2016
comment
Вы можете отказаться от QSortFilterProxyModel и заменить его собственной реализацией; поскольку вы знаете конкретные потребности своего приложения, вы можете спроектировать свою реализацию так, чтобы она была более эффективной, чем QSortFilterProxyModel, которая должна быть универсальной и не может делать никаких предположений о поведении кода, с которым она взаимодействует.   -  person Jeremy Friesner    schedule 14.10.2016
comment
@JeremyFriesner. Итак, вы говорите, что мы должны забыть QSortFilterProxyModel. Было бы неплохо реализовать фильтрацию внутри модели исходных данных (полученной из QAbstractTableModel), если нам нужна только фильтрация? если нет, я прислушаюсь к вашей рекомендации.   -  person IMAN4K    schedule 14.10.2016
comment
@DmitrySazonov.Если я прав, вы подтверждаете: используя строки вместо RegExp и в собственных методах (установщиках), мы устанавливаем строку фильтра? На самом деле у меня нет проблем с получением данных из MSSQL в мою модель, и, как я уже сказал, это очень быстро для 200 000 строк, и когда мы используем фильтрацию, данные (это представление использует их) находятся в памяти   -  person IMAN4K    schedule 14.10.2016
comment
При использовании && постарайтесь помочь ему быстро выйти из строя - сначала ставьте наименее вероятный случай. Это уменьшит количество вычислений последующих ветвей конъюнкции.   -  person Toby Speight    schedule 14.10.2016


Ответы (1)


Если количество элементов очень велико, и вы не можете загрузить их за один раз, вы можете попробовать добавлять элементы в пакетах только тогда, когда они необходимы в представлении. Это можно сделать, переопределив canFetchMore() и fetchMore(). Взгляните на дополнительный пример. Обратите внимание, что именно так QSqlQueryModel внутренне загружает большие модели из баз данных, см. здесь.

Вот как ваша модель может быть реализована с использованием этого подхода:

#include <QApplication>
#include <QtWidgets>

class MyTableModel : public QAbstractTableModel{
public:
    explicit MyTableModel(int rowCount, QObject* parent=nullptr)
        :QAbstractTableModel(parent),currentRowCount(0),wholeRowCount(rowCount){}
    ~MyTableModel(){}

    int rowCount(const QModelIndex &parent) const override{
        if(parent.isValid()) return 0;
        return currentRowCount;
    }
    int columnCount(const QModelIndex &parent) const override{
        if(parent.isValid()) return 0;
        return 2;
    }

    QVariant data(const QModelIndex &index, int role) const override{
        Q_ASSERT(index.row()<currentRowCount);
        QVariant val;
        if(role== Qt::DisplayRole || role== Qt::EditRole){
            switch(index.column()){
            case 0:
                val= QString("#%1").arg(index.row()+1, 8, 10, QChar('0'));
                break;
            case 1:
                val= rows[index.row()];
                break;
            }
        }
        return val;
    }

    bool canFetchMore(const QModelIndex &parent) const override{
        if(parent.isValid()) return false;
        return (currentRowCount < wholeRowCount);
    }

    void fetchMore(const QModelIndex& /* index */) override{
        int toFetch= qMin(52, wholeRowCount-currentRowCount);
        char ch = 'A';
        beginInsertRows(QModelIndex(), currentRowCount, currentRowCount+toFetch-1);
        for(int i=0; i<toFetch; i++){
            rows+= QString(QChar(ch));
            if(ch == 'Z') ch = 'A';
            else ch++;
        }
        currentRowCount+= toFetch;
        endInsertRows();
    }

private:
    int currentRowCount;
    int wholeRowCount;
    QStringList rows;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget w;
    QVBoxLayout layout(&w);
    QLineEdit filterLineEdit;
    QTableView tableView;
    layout.addWidget(&filterLineEdit);
    layout.addWidget(&tableView);

    MyTableModel model(200000);
    QSortFilterProxyModel proxyModel;
    proxyModel.setSourceModel(&model);
    proxyModel.setFilterKeyColumn(-1);
    tableView.setModel(&proxyModel);

    QObject::connect(&filterLineEdit, &QLineEdit::textChanged, [&](){
        proxyModel.setFilterFixedString(filterLineEdit.text());
    });

    w.show();

    return a.exec();
}

Если вы уверены, что вашим настоящим узким местом является фильтрация, вы также можете избегать использования регулярных выражений, как отметил @DmitrySazonov, подкласс QSortFilterProxyModel, переопределить filterAcceptsRow() и укажите там свой алгоритм вместо использования обычных фильтров на основе QRegExp.

Еще одна вещь, которую следует учитывать, - избегать проверки уже отфильтрованных строк, когда фильтр стал уже, посмотрите на этот вопрос.

person Mike    schedule 14.10.2016
comment
Я ценю ваш ответ. Но я почти уверен, что моя модель работает хорошо, я реализовал ее с быстрой внутренней структурой, и я могу загрузить 200 тыс. строк всего за секунду (в конфигурации release). и в течение этого времени я показываю индикатор выполнения без необходимость пейджинга. Моя проблема сейчас - фильтрация в реальном времени с помощью QSortFilterProxyModel. не могли бы вы немного объяснить о algorithm instead of QRegExp-based filter, это больше, чем сравнение строк? - person IMAN4K; 14.10.2016
comment
Как вы показываете индикатор выполнения в течение этого времени? Если ваша модель загружается в основном потоке, индикатор выполнения не может быть обновлен до тех пор, пока управление не будет возвращено в цикл событий (когда загрузка завершена). Я думаю, вы делаете что-то не так. - person Mike; 14.10.2016
comment
@ IMAN4K, я имею в виду предоставление пользовательской реализации для filterAcceptsRow(), которая не зависит от регулярных выражений (вместо этого она работает лучше из-за вашего конкретного типа фильтрации). Тем не менее, я действительно думаю, что регулярные выражения в Qt очень хорошо оптимизированы, и вам не следует идти по этому пути, если вы не уверены, что ваша фильтрация имеет очень специфические потребности, которые отличаются от обычного сопоставления строк и регулярных выражений. - person Mike; 14.10.2016
comment
@Mike.Использование потока: я запускаю свой запрос в рабочем объекте, а также заполняю свою структуру, и после завершения я выдаю сигнал успеха (в этом процессе представление скрыто и отображается пользовательская панель выполнения), когда выдается сигнал успеха, я дам моя структура моделируется, а затем вызывается view::show(). Все это происходит примерно за 1 секунду, не блокируя основной цикл. - person IMAN4K; 14.10.2016
comment
@ IMAN4K, в репозитории github, на который вы ссылались, я не смог найти никакого использования потоков, поэтому я задал свой вопрос. Итак, вы ищете способ сделать фильтрацию, например, в другом потоке? Если это так, вы можете задать новый вопрос конкретно об этом и сообщить, что вы пробовали. . . - person Mike; 14.10.2016
comment
@ Использование потока для фильтрации - это не то, что я хочу, чтобы пользователь сразу увидел результат. Не могли бы вы взглянуть на мое редактирование. Теперь это работает хорошо. Вы рекомендуете такой способ? - person IMAN4K; 14.10.2016
comment
@ IMAN4K, если вы действительно видите существенную разницу, все в порядке. - person Mike; 14.10.2016