QByteArray与reserve()的内部重新分配行为

Internal reallocation behaviour of QByteArray vs reserve()

本文关键字:分配 新分配 内部 reserve QByteArray      更新时间:2023-10-16

我只是试图优化一些通信堆栈。我使用的是Qt 5.3.2/VS2013。

堆栈使用QByteArray作为数据缓冲区。我打算使用capacity()reserve()方法来减少在数据大小增长时不必要的内部缓冲区重新分配。然而,事实证明QByteArray的行为是不一致的。保留的空间有时似乎被含蓄地挤压了。

我可以提取下面的演示,将字符串附加、字符串分配和字符附加到三个缓冲区。这些单个操作似乎保留了内部缓冲区大小(使用capacity()获得)。然而,当将这三个操作中的每一个应用于同一QByteArray时,保留的大小会发生变化。这种行为在我看来是随机的:

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;

预期输出为:

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

但实际输出是:

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

有人能解释这种奇怪的行为吗?

更新:看起来clear()也放弃了预订。

好的。我想我得到了我需要的信息。

显然,预订并不是通过所有方法来维持的。特别是clear()operator=()似乎取消了预订。在operator=()的情况下,由于operator=(QByteArray)使用的数据的隐式共享,实际上将不可能保留保留。

这也意味着QByteArray的保留机制是针对不同的用例而制定的。尝试在QByteArray对象的整个生命周期中保持保留是很困难的。

对于我的用例,似乎有一个使用truncate(0)而不是clear()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;

此打印:

buffer 1000 "foo"
buffer 1000 "bar"

(感谢Alejandro)

更稳定的方法是在每个数据收集/附加序列之前进行reserve()调用。这并没有将QByteArray的整个生命中的重新分配减少到一次,但至少它在每个数据序列中只使用一次重新分配,否则需要多次重新分配。我认为这是一个可以接受的变通办法。

无论如何,在Qt容器上使用reserve()之前,应该详细测试其行为,否则容器的行为可能会与预期大不相同。这一点也很重要,因为这些重要的实现细节没有记录在案,并且在未来的Qt版本中可能会发生更改,而无需另行通知。

QByteArray中的operator=包含下一个代码

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

它重新分配了内存,如果新的数据长度大于分配的数据长度,或者如果新数据长度小于当前大小且小于(已分配>>1)

在QByteArray类中没有直接的方法可以利用reserve()方法。我试着做以下操作:

QByteArray buffer;
buffer.reserve(1000);
buffer.append("foo");
cout << "Capacity " << buffer.capacity() << " Buffer " << buffer;
buffer.truncate(0);
buffer.append("bar");
cout << "Capacity " << buffer.capacity() << " Buffer " << buffer;

输出为:

Capacity 1000 Buffer foo
Capacity 8 Buffer bar

之所以会发生这种情况,是因为在缓冲区上调用truncate会调用"resize"方法,该方法实际上会释放保留内存。要使其发挥作用,没有一种简单的方法可以使用有文档记录的QByteArray API。我可以想出一种方法,可以尝试一下。

QByteArray buffer;
buffer.reserve(1000);
buffer.append("foo");
cout << "Capacity " << buffer.capacity() << " Buffer " << buffer;
buffer.fill('');
buffer.data_ptr()->size = 0;
buffer.append("bar");
cout << "Capacity " << buffer.capacity() << " Buffer " << buffer;

输出为:

Capacity 1000 Buffer foo
Capacity 1000 Buffer bar

线路:

buffer.fill('');

将现有缓冲区的内容设置为NULL。它是可选使用的。

线路:

buffer.data_ptr()->size = 0;

data_ptr()方法返回内部结构data的指针,然后通过将其大小变量修改为0,使缓冲区为空。因此,进一步调用append是合法的。