Я использую QListView
с пользовательской моделью, полученной из QAbstractItemModel
. У меня порядка миллиона предметов. Я вызвал listView->setUniformItemSizes(true)
, чтобы предотвратить вызов логики компоновки при добавлении элементов в модель. Пока все работает так, как ожидалось.
Проблема в том, что использование клавиатуры для навигации по списку происходит медленно. Если я выбираю элемент в списке, а затем нажимаю вверх/вниз, выбор перемещается быстро, пока выбор не требует прокрутки списка. Затем он становится очень медленным. Нажатие page-up или page-down тоже очень тормозит. Кажется, проблема заключается в том, что элемент выбран (он же «текущий элемент») с помощью клавиатуры, а список также прокручивается вверх/вниз.
Если я использую мышь, навигация по списку выполняется быстро. Я могу использовать колесо мыши, которое быстро. Я могу перетаскивать полосу прокрутки вверх/вниз так быстро, как захочу — от верхней части списка к нижней — и вид списка обновляется ужасно быстро.
Любые идеи о том, почему комбинация изменения выбора и прокрутки списка так медленна? Есть ли жизнеспособный обходной путь?
Обновление от 09.09.15
Чтобы лучше проиллюстрировать проблему, я предоставляю дополнительную информацию в этом обновлении.
Проблемы с производительностью при использовании KEYBOARD + SCROLLING
В основном это вопрос производительности, хотя он в некоторой степени связан с пользовательским опытом (UX). Посмотрите, что происходит, когда я использую клавиатуру для прокрутки QListView
:
Заметили замедление в нижней части страницы? Это главный вопрос моего вопроса. Позвольте мне объяснить, как я перемещаюсь по списку.
Пояснение:
- Начиная сверху, выбирается первый элемент в списке.
- Нажав и удерживая клавишу со стрелкой вниз, текущий элемент (выбор) изменяется на следующий элемент.
- Изменение выбора происходит быстро для всех элементов, которые в данный момент находятся в поле зрения.
- Как только список должен вывести следующий элемент в поле зрения, скорость выбора значительно замедляется.
Я ожидаю, что список должен иметь возможность прокручиваться со скоростью набора текста на моей клавиатуре — другими словами, время, необходимое для выбора следующего элемента, не должно замедляться при прокрутке списка.
Быстрая прокрутка с помощью МЫШИ
Вот как это выглядит, когда я использую мышь:
Пояснение:
- С помощью мыши я выбираю ручку полосы прокрутки.
- Быстро перетаскивая ручку полосы прокрутки вверх и вниз, список прокручивается соответственно.
- Все движения очень быстрые.
- Обратите внимание, что выборы не выполняются.
Это доказывает два основных момента:
Проблема не в модели. Как видите, у модели нет проблем с производительностью. Он может доставлять элементы быстрее, чем они могут отображаться.
При выборе И прокрутке производительность снижается. "Идеальный шторм" выбора и прокрутки (как показано с помощью клавиатуры для навигации по списку) вызывает замедление. В результате я предполагаю, что Qt каким-то образом выполняет большую обработку, когда выбор делается во время прокрутки, что обычно не выполняется.
Реализация не на Qt выполняется БЫСТРО
Я хочу отметить, что моя проблема, похоже, специфична для Qt.
Я уже реализовал этот тип вещей, прежде чем использовать другую структуру. То, что я пытаюсь сделать, находится в рамках теории представления модели. Я могу делать именно то, что описываю, на молниеносной скорости, используя juce ::ListBoxModel с juce::ListBox< /а>. Это глупо быстро (плюс, нет необходимости создавать повторяющийся индекс, такой как QModelIndex
, для каждого отдельного элемента, когда каждый элемент уже имеет уникальный индекс). Я понимаю, что Qt нуждается в QModelIndex
для каждого элемента в своей архитектуре представления модели, и хотя мне не нравятся накладные расходы, я думаю, что понял рациональность и могу с этим смириться. В любом случае, я не подозреваю, что именно эти QModelIndex
es вызывают снижение производительности.
С реализацией JUCE я могу даже использовать клавиши page-up и page-down для навигации по списку, и он просто проносится по списку. Используя реализацию Qt QListView
, он работает с пыхтением и лагает, даже с релизной сборкой.
Реализация модели-представления с использованием JUCE framework выполняется очень быстро. Почему реализация Qt QListView
такая плохая?!
Мотивирующий пример
Трудно представить, зачем вам нужно так много элементов в представлении списка? Что ж, мы все уже видели подобное:
Это индекс средства просмотра справки Visual Studio. Я не считал все предметы, но думаю, мы согласимся, что их очень много! Конечно, чтобы сделать этот список «полезным», они добавили поле фильтра, которое сужает то, что находится в представлении списка, в соответствии с входной строкой. Здесь нет никаких хитростей. Все это практичные, реальные вещи, которые мы все десятилетиями видели в настольных приложениях.
Но существуют ли миллионы элементов? Я не уверен, что это имеет значение. Даже если бы было «всего» 150 000 элементов (что является примерно точным на основе некоторых грубых измерений), легко указать, что вам нужно что-то сделать, чтобы сделать его пригодным для использования — и это то, что фильтр сделает для вас.
В моем конкретном примере используется список немецких слов в виде обычного текстового файла с чуть более чем 1,7 миллионами записей (включая изменчивые формы). Это, вероятно, только частичная (но все же значительная) выборка слов из немецкого текстового корпуса, который использовался составить этот список. Для лингвистических исследований это разумный вариант использования.
Забота об улучшении UX (пользовательского опыта) или фильтрации — отличные цели дизайна, но они выходят за рамки этого вопроса (я обязательно обращусь к ним позже в проекте).
Код
Хотите пример кода? Ты получил это! Я не уверен, насколько это будет полезно; это настолько ванильно, насколько это возможно (около 75% шаблонного), но я полагаю, что это обеспечит некоторый контекст. Я понимаю, что использую QStringList
и что для этого есть QStringListModel
, но QStringList
, который я использую для хранения данных, является заполнителем - модель в конечном итоге будет несколько сложнее, поэтому, в конце концов, я необходимо использовать пользовательскую модель, полученную из QAbstractItemModel
.
//
// wordlistmodel.h ///////////////////////////////////////
//
class WordListModel : public QAbstractItemModel
{
Q_OBJECT
public:
WordListModel(QObject* parent = 0);
virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex& index) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
public slots:
void loadWords();
signals:
void wordAdded();
private:
// TODO: this is a temp backing store for the data
QStringList wordList;
};
//
// wordlistmodel.cpp ///////////////////////////////////////
//
WordListModel::WordListModel(QObject* parent) :
QAbstractItemModel(parent)
{
wordList.reserve(1605572 + 50); // testing purposes only!
}
void WordListModel::loadWords()
{
// load items from file or database
// Due to taking Kuba Ober's advice to call setUniformItemSizes(true),
// loading is fast. I'm not using a background thread to do
// loading because I was trying to visually benchmark loading speed.
// Besides, I am going to use a completely different method using
// an in-memory file or a database, so optimizing this loading by
// putting it in a background thread would obfuscate things.
// Loading isn't a problem or the point of my question; it takes
// less than a second to load all 1.6 million items.
QFile file("german.dic");
if (!file.exists() || !file.open(QIODevice::ReadOnly))
{
QMessageBox::critical(
0,
QString("File error"),
"Unable to open " + file.fileName() + ". Make sure it can be located in " +
QDir::currentPath()
);
}
else
{
QTextStream stream(&file);
int numRowsBefore = wordList.size();
int row = 0;
while (!stream.atEnd())
{
// This works for testing, but it's not optimal.
// My real solution will use a completely different
// backing store (memory mapped file or database),
// so I'm not going to put the gory details here.
wordList.append(stream.readLine());
++row;
if (row % 10000 == 0)
{
// visual benchmark to see how fast items
// can be loaded. Don't do this in real code;
// this is a hack. I know.
emit wordAdded();
QApplication::processEvents();
}
}
if (row > 0)
{
// update final word count
emit wordAdded();
QApplication::processEvents();
// It's dumb that I need to know how many items I
// am adding *before* calling beginInsertRows().
// So my begin/end block is empty because I don't know
// in advance how many items I have, and I don't want
// to pre-process the list just to count the number
// of items. But, this gets the job done.
beginInsertRows(QModelIndex(), numRowsBefore, numRowsBefore + row - 1);
endInsertRows();
}
}
}
QModelIndex WordListModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || column < 0)
return QModelIndex();
else
return createIndex(row, column);
}
QModelIndex WordListModel::parent(const QModelIndex& index) const
{
return QModelIndex(); // this is used as the parent index
}
int WordListModel::rowCount(const QModelIndex& parent) const
{
return wordList.size();
}
int WordListModel::columnCount(const QModelIndex& parent) const
{
return 1; // it's a list
}
QVariant WordListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
else if (role == Qt::DisplayRole)
{
return wordList.at(index.row());
}
else
{
return QVariant();
}
}
//
// mainwindow.h ///////////////////////////////////////
//
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void updateWordCount();
private:
Ui::MainWindow *ui;
WordListModel* wordListModel;
};
//
// mainwindow.cpp ///////////////////////////////////////
//
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->listView->setModel(wordListModel = new WordListModel(this));
// this saves TONS of time during loading,
// but selecting/scrolling performance wasn't improved
ui->listView->setUniformItemSizes(true);
// these didn't help selecting/scrolling performance...
//ui->listView->setLayoutMode(QListView::Batched);
//ui->listView->setBatchSize(100);
connect(
ui->pushButtonLoadWords,
SIGNAL(clicked(bool)),
wordListModel,
SLOT(loadWords())
);
connect(
wordListModel,
SIGNAL(wordAdded()),
this,
SLOT(updateWordCount())
);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateWordCount()
{
QString wordCount;
wordCount.setNum(wordListModel->rowCount());
ui->labelNumWordsLoaded->setText(wordCount);
}
Как уже отмечалось, я уже рассмотрел и принял совет Кубы Обера:
QListView слишком долго обновляется при наличии 100 тыс. элементов
Мой вопрос не дублирует этот вопрос! В другом вопросе ОП спрашивал о скорости загрузки, которая, как я отметил в своем коде выше, не проблема из-за вызова setUniformItemSizes(true)
.
Сводные вопросы
- Почему навигация по
QListView
(с миллионами элементов в модели) с помощью клавиатуры происходит так медленно, когда список прокручивается? - Почему сочетание выбора и прокрутки элементов вызывает замедление?
- Есть ли какие-либо детали реализации, которые я упустил, или я достиг порога производительности для
QListView
?
QListView
иQAbstractItemModel
, что я и пытаюсь выяснить. Я хочу докопаться до первопричины. - person Matthew Kraus   schedule 09.09.2015QListView
в этом отношении просто недоработан - в нем нет ничего, что ограничивало бы его производительность, просто текущая реализация несовершенна. Я бы посоветовал вам получить учетную запись gerrit, настроить git и исправить ее. В любом случае вам нужно будет собрать Qt из исходного кода, чтобы отследить Qt и диагностировать, почему он работает медленно, поэтому его исправление находится недалеко от этого :) - person Kuba hasn't forgotten Monica   schedule 09.09.2015QListView
, а не то, что можно легко исправить. Если я получу пропускную способность, это будет интересная попытка... - person Matthew Kraus   schedule 09.09.2015