Subnavigation

Behavioral changes in QIODevice in Qt 4.2

Some people think QFile in Qt 4 is slow[*]. Compared to Qt 3, the noticable difference lies in QIODevice::getChar(), and to some extent, QIODevice::putChar(). But there's good news: With Qt 4.2, we've managed to improve QIODevice's performance by 4-5x when reading small blocks at a time, especially so for getChar() :-).

So something has changed with QIODevice in the upcoming Qt 4.2, and for those of you who write QIODevice subclasses, this write-up could be worth reading.

For existing subclasses of QIODevice in Qt, such as QBuffer, QFile, QTcpSocket, and QProcess, you won't notice any difference (other than the speed-up). Reading character by character is now mostly on par with Qt 3. The cost of this optimization is a change in behavior for custom device authors.

We've upgraded the unget-buffer in QIODevice to a general purpose buffer. When in 4.0 and 4.1, a getChar() or readLine() call used to access QIODevice::readData() directly, these calls can now read blocks from an internal buffer instead. The speed-up is very noticable; especially so for QFile, which forwards readData() calls to its file engine, rendering the compiler helpless with regards to optimizations.

The behavioral change only affects custom QIODevice subclasses that didn't handle unget before. Because unget is a fairly rare operation, though, you could easily get away with this in 4.0 and 4.1. Let's take an example. You may find the following construct familiar:

bool SpeedDevice::atEnd()
{
return index == data.size();
}

An unget operation on such a device breaks atEnd(), because there's a character in QIODevice's unget buffer that SpeedDevice doesn't know of:

if (speedDevice.atEnd())
{
speedDevice.ungetChar('a');
if (speedDevice.atEnd()) { /* still true */ }
}

To avoid the problem with atEnd() falling out of sync, you would improve the first call by calling the base implementation:

bool SpeedDevice::atEnd()
{
return index == data.size() && QIODevice::atEnd();
}

There, now it works fine. If you did this consistently in 4.1, you won't have any problem with 4.2. With the first implementation, however, your subclass will stop working; in this example, atEnd() is likely to return false negatives.

The following functions are affected by QIODevice's buffer:

    QIODevice::atEnd()
QIODevice::bytesAvailable()
QIODevice::size()
QIODevice::canReadLine()

And the fix is: call the base implementation after checking your own conditions. It's the only way to keep QIODevice's buffer in on the deal.

Here's a couple of special ones:

    QIODevice::pos()
QIODevice::seek()

You shouldn't have to reimplement QIODevice::pos() ever - if you do, it should end up calling QIODevice::pos()'s base implementation. QIODevice maintains your device's position for you. Your device is always in sync as long as you reimplement QIODevice::seek(). And recall that you should also call QIODevice::seek()'s base implementation after syncing your own device.

QIODevice itself guarantees that when readData() or writeData() are called, your device will be in sync (so you don't ever have to call seek() inside those virtual functions).

So there you have it! These changes will also be described in the 4.2 change log.

:bibr

PS: IO devices opened in QIODevice::Unbuffered mode are unaffected by the changes to QIODevice in 4.2.

PPS: This change renders my trick from QQ17 (TCP Traffic Control) unusable; instead, the rate-controlled socket has to proxy an internal QTcpSocket instance. The examples/network/torrent example in Qt 4.2 will demonstrate what changes were necessary.

[*]: Our usual response is: Either you can read large blocks at a time, or you can use QTextStream - both are very fast.


Blog Topics:

Comments