In my previous blog post, I wrote about the new TableView for Qt-5.12. What I didn’t mention was how the new TableView performs compared to the old TableView in QtQuick Controls 1. However, the old version had some serious performance issues, which is what led us to implement a new one from scratch. The reason for the bad performance comes from the fact that it’s written on top of ListView. But ListView is designed and optimized to show only one column, which of course is problematic when you try to use it to show a table with multiple columns.
To work around this limitation, the old TableView implements a little hack: it takes each column delegate and puts them side-by-side to create one fat row delegate. From ListViews point of view, it looks like a normal list delegate. The result is that whenever a new row is flicked in, all the items inside that delegate (which is one item for each column) will be instantiated in one go. Although this is not a disaster for a table with only a handfull of columns, performance takes a major hit when a table is of a non-trivial size. And to be fair, the old TableView was never designed to handle anything else. But for tables where you have, lets say, hundred columns or more, you will create hundred new items for each row flicked in. And most of them ends up hidden outside the viewport. And that is actually the best case; a delegate is normally composed of many items, so the item count will be even higher. The video underneath shows how scrolling can grind to a halt when using a model with only thirty columns.
The new TableView doesn’t suffer from the same problems. It’s written directly in C++ on top of Flickable (not ListView), and optimized to show any number of columns. Most importantly, compared to the old version, it’ll only create and show columns that actually ends up visible on screen. That alone will of course have a dramatic improvement. The same is true when calculating the various sizes of the view, like the table width or the individual column widths: it will only focus on what is loaded and visible, and use that for calculating and predicting the rest. A declarative API for setting e.g all the column widths would for sure be nice for small tables, but just doesn’t scale for models with potentially hundreds, or even thousands of columns. Here we had to make a design decision, where we ended up using a columnWidthProvider to favor speed and flexibility, over a more traditional declarative API like the one used in the old version. The new TableView is basically designed from the ground up to have the same performance and memory usage irrespectively of the size of the model.
Another important improvement is the reusing of delegate items. When applied, it doesn’t really matter if a delegate is a composition of many items, as you don’t end up instantiating them over and over anyway. While the old TableView is actually making an effort to reuse items, it somewhat fails because it’s trying to make this process transparent to the application. As such, when a row is pooled, it unbinds model properties and clear variables, which can lead to a chain reaction of signals being emitted, which again can lead to unnecessary layouts and repaints. The more columns, the more items to reset. Compare this to the new TableView, which is instead completely open about it, and do as little as possible during the same process. It basically leaves it to the developer to decide what needs to be done, if anything, to a reused item. This will lower the cost even more. It also helps, of course, that the reuse implementation for the new version is not a hack on top of ListView, but instead written directly into the model and view classes from C++.
The video underneath shows the new TableView working on a model with 50,000 columns without performance problems.
To sum up, it is not surprising that the performance of the new TableView is on a whole different level than the old version. The old version was written to temporarily fill a hole in the QtQuick Controls 1 desktop offering, and was never intended to be used for models with a high column count. The new version has a higher performance, but it’s also more bare-bone. It aims to have the same usage, flexibility and speed as ListView, but in two dimensions instead of one.