Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Internal reallocation behaviour of QByteArray vs reserve()

I just tried to optimize some communication stack. I am using Qt 5.3.2 / VS2013.

The stack uses a QByteArray as a data buffer. I intended to use the capacity() and reserve() methods to reduce unnecessary internal buffer reallocations while the data size grows. However the behaviour of QByteArray turned out to be inconsistent. The reserved space sometimes seems to be squeezed implicitly.

I could extract the following demo applying a string append, a string assignment and a character append to three buffers. These single operations seem to preserve the internal buffers size (obtained using capacity()). However when applying each of these three operations to the same QByteArray the reserved size changes. The behaviour looks random to me:

QByteArray x1; x1.reserve(1000);
x1.append("test");
qDebug() << "x1" << x1.capacity() << x1;

QByteArray x2; x2.reserve(1000);
x2 = "test";
qDebug() << "x2" << x2.capacity() << x2;

QByteArray x3; x3.reserve(1000);
x3.append('t');
qDebug() << "x3" << x3.capacity() << x3;

QByteArray x4; x4.reserve(1000);
x4.append("test");
x4.append('t');
x4 = "test";
qDebug() << "x4" << x4.capacity() << x4;

The expected output would be:

x1 1000 "test"
x2 1000 "test"
x3 1000 "t"
x4 1000 "test"

But the actual output is:

x1 1000 "test"
x2 1000 "test"
x3 1000 "t"
x4 4 "test"

Does anyone have an explanation for that strange behaviour?

UPDATE: Looks like clear() also discards reservation.

like image 970
Silicomancer Avatar asked Nov 08 '14 19:11

Silicomancer


2 Answers

Ok. I think I got the information I need.

Obviously the reservation isn't maintained beyond all methods. Especially clear() and operator=() seem to cancel the reservation. In case of operator=() it actually will be impossible to preserve the reservation due to implicit sharing of data that is used by operator=(QByteArray).

This also implies that reservation mechanism of QByteArray is made for a different use case. Trying to make the reservation persist during the entire life of the QByteArray object is difficult.

For my use case there seems to be a workaround using truncate(0) instead of clear() or operator=():

QByteArray buffer;
buffer.reserve(1000);
buffer.append("foo");
qDebug() << "buffer" << buffer.capacity() << buffer;

buffer.truncate(0);
buffer.append("bar");
qDebug() << "buffer" << buffer.capacity() << buffer;

This prints:

buffer 1000 "foo"
buffer 1000 "bar"

(Thanks Alejandro)

Still the more stable approach will be to do an reserve() call before every data collection/appending sequence. This does not reduce the reallocation to one through the entire life of the QByteArray but at least it uses exactly one reallocation per data sequence where it would need many reallocations otherwise. I think this is an acceptable workaround.

Anyway before using reserve() on a Qt container one should test the behaviour in detail because otherwise it may happen that the container behaves much different than expected. This also is important since those essential implementation details are not documented and could change without further notice in future Qt versions.

like image 75
Silicomancer Avatar answered Sep 18 '22 22:09

Silicomancer


operator= in QByteArray contains next code

int len = qstrlen(str);
if (d->ref != 1 || len > d->alloc || (len < d->size && len < d->alloc >> 1))
realloc(len);

It reallocated memory, if new data length more then allocated or if new data length less then current size and less then (allocated >> 1)

like image 37
Meefte Avatar answered Sep 19 '22 22:09

Meefte