[Solved] Input needed: Creating an efficient QAbstractItemModel-subclass
I have been working on an application that uses a QStandardItemModel. Since the model can get somewhat large, I was looking for ways to decrease its memory footprint. And after reading through the Qt documentation I decided to implement my own model.
I found one or maybe two possible candidates: QAbstractItemModel and QAbstractTableModel. Looking at the data that is going to be saved, QAbstractTableModel might be the easier choice - there is no need for children of items and I pretty much know what data is going to be saved.
It seems that the abstract models do not come with a way to actually save data - so I have to pick a suitable format for that, too. And with so many variables I hope that someone with experience can give me some hints:
1. How to actually memorize the data of the model? Qt (shockingly) has no container class for multidimensional data. I looked into QVector<QVector<QString> > and QList<QList<QString> >.
2. I tried to replace the QString with a QByteArray but for some reason that used a lot more memory than I was expecting - I would like to know why.
- Using a nested list like this can makes insertions along one of the two axes cumbersome (and possibly slow). Is there a way around this?
4. Would there be any drawbacks in comparison to QStandardItemModel?
- If I decided that I needed a bit more data than just a QString (e.g. a QFont and maybe a QColor), what would be the best way of adding that data? I can only think of a struct (QMap is probably to 'heavy'), or maybe (in this example) I could somehow use rich text inside the QString. But how much performance would that eat up in comparison to a struct?
6. QAbstractItemModel by default works with QVariants instead of QStrings. How much performance/memory does that approach eat up?
As I said: I read large portions of the documentation concerning this topic, and I already implemented a QAbstractTableModel for testing purposes. I am merely looking for some advice, hopefully somebody has any kind of experience with this. If so, please share it with me. :)
Meanwhile I finished this "project". It had an overwhelmingly good effect: RAM-usage went down about 60%, writing to/reading out the model is incredibly fast (although it makes a big difference if the internal data is stored as rows or columns, so that's something that might depend on the use-case). Even millions of cells can be pretty easily managed by a custom model where QStandardItemModel brought the application to crash at only a couple hundred thousand.
The Abstract classes don't include working code, only functions to make sure your view and model understand each other. you need to implement them yourself. The AbstractTableModel inherits from QAbstractItemModel, so basically the same, except the table model does some stuff for tables already, saving some time for you when otherwise using the ItemModel.
Did you read the Model/View docs??: "Model/View Qt4.8":http://qt-project.org/doc/qt-4.8/model-view-programming.html#model-subclassing-reference
1: The multidimensional option for Qt containers is doable, but i always prefer a qlist of structs. The struct should then be made an template available type by the Q_DECLARE_METATYPE. Then you are also able to use the signal/slot for transferring the struct. Every struct has data that hold one line, since it's a table, this is easy.
2: A QString is only a pointer to a string reference. An empty string is thus 4 bytes (32 bits system) big. A QByteArray has more then that.
3: Nup, insertion in a list is quick. QList is build up out of pointers, not of complete classes. So allocating memory and then insert a pointer in the list is easy and quick. No need to shift all allocated memory! The memory used in the QList is not in sequence in the stack.
4: Not really, it totally depends on what you need. When you have a "standard" table, keep the QAbstractTableModel.
5: See point 1, use a struct or add a class containing the usable data.
6: Almost none. A QVariant is simply a pointer to memory, so the QVariant is the same size as a bit/byte/char etc. It comes from a super lightweight ideology. Only the conversions may take a bit of extra time, from QVariant to string/char etc.
Hope this helps some!
Thanks for your answer :) . At some points I haven't been clear enough, I think.
I know what an abstract class is. And yes, I read all the documentation pages I could find - from high- to low-level.
A struct per row? I will keep that in mind, but I cannot tell yet if that's compatible with my needs.
Unless an empty QString is a mere null-pointer, it certainly uses up more than four bytes. I was talking about overall memory usage, not the size of the list itself. And usually QByteArray has been a good choice for me when looking for a well-performing alternative to QString. Not so this time, it seems.
But in the end QString is probably much easier to use anyway, I was just curious.
I was referring to the nested list: You can always easily add something to the outer list (in my case that one contains the rows). But since the nested list represents a table, how would I add a column? I have to add one element to every row, which is a lot of work in comparison to adding a single row.
In terms of complexity: adding a row = O(1), adding a column = O(N) - or vice versa if I chose a different design, but the problem persists in all cases.
A class per row? I might try it, but I wanted to have a simple and fast alternative to QStandardItemModel (which is actually quite fast but uses huge amounts of RAM).
And the QVariant-conversion is necessary for every read/write operation, that is why I asked. I don't need this level of flexibility if I am custom-tailoring a model, but I wanted to know how much the restriction from QVariant to QString could actually gain me.
I have an additional questions:
@QList<QVector<QString> > items;
What exactly happens in line two? Is a vector created and then copied? Since QList only saves pointers and this variable is put on the stack, I would assume that the following is the better approach:
@QList<QVector<QString> > items;
QVector<QString> *newItem = new QVector<QString>(10);
Or does it not make a difference?