【问题标题】:Internal reallocation behaviour of QByteArray vs reserve()QByteArray 与 reserve() 的内部重新分配行为
【发布时间】:2015-01-05 10:09:24
【问题描述】:

我只是尝试优化一些通信堆栈。我正在使用 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() 也放弃了预订。

【问题讨论】:

  • 请再看一遍... x2 使用相同的分配。这不是分配......不知何故,是导致问题的操作组合。
  • 对。对于那个很抱歉。 x2 分配给一个已分配的空缓冲区,x4 重置缓冲区....
  • 你认为这实际上是一种期望的行为吗?
  • 我正在查看 QByteArray 中 operator= 的代码......我不太了解其中的逻辑。它不适合您的用例,保留容量在很多情况下都不会保持。 (看看其他调整大小/截断的东西,看起来并不好。)
  • (此外,如果您实际测量到这些分配会花费您,那么 QByteArray 的隐式共享模型的开销本身对于您的用例来说可能开销太大。)

标签: c++ qt memory-management qbytearray


【解决方案1】:

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

显然,保留并没有超出所有方法。特别是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"

(感谢亚历杭德罗)

更稳定的方法是在每个数据收集/附加序列之前调用reserve()。这不会在 QByteArray 的整个 life 中将重新分配减少到一个,但至少它对每个数据序列使用一个重新分配,否则它需要多次重新分配。我认为这是一个可以接受的解决方法。

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 在这种情况下我失踪有什么意义吗?如果保留空间这么容易被丢弃,reserve() 有什么意义?
    • @Silicomancer 我真的不明白为什么operator= 这样做。 append() 仅当 (d-&gt;ref != 1 || d-&gt;size + len &gt; d-&gt;alloc) 时才重新分配内存。所以如果你使用append(),没有operator=reserve()按预期工作
    【解决方案3】:

    没有直接的方法来利用 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('\0');
    buffer.data_ptr()->size = 0;
    buffer.append("bar");
    cout << "Capacity " << buffer.capacity() << " Buffer " << buffer;
    

    输出是:

    Capacity 1000 Buffer foo
    Capacity 1000 Buffer bar
    

    行:

    buffer.fill('\0');
    

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

    行:

    buffer.data_ptr()->size = 0;
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-05
      • 2011-02-24
      • 2015-09-18
      • 1970-01-01
      相关资源
      最近更新 更多