Внутреннее поведение перераспределения QByteArray против Reserve()

Я просто попытался оптимизировать некоторый стек связи. Я использую 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() также отказывается от бронирования.

3 ответа

Решение

Хорошо. Я думаю, что получил информацию, которая мне нужна.

Очевидно, что бронирование не поддерживается за пределами всех методов. Особенно 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, но, по крайней мере, оно использует ровно одно перераспределение на последовательность данных, где в противном случае потребуется много перераспределений. Я думаю, что это приемлемый обходной путь.

Во всяком случае перед использованием reserve() в контейнере Qt нужно подробно протестировать поведение, потому что в противном случае контейнер может вести себя совершенно иначе, чем ожидалось. Это также важно, поскольку эти важные детали реализации не документированы и могут быть изменены без предварительного уведомления в будущих версиях Qt.

operator= в QByteArray содержит следующий код

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

Он перераспределяет память, если длина новых данных больше выделенной или если длина новых данных меньше текущего размера и меньше (выделено >> 1)

Нет простого способа воспользоваться преимуществом метода reserve() в классе QByteArray. Я попытался сделать следующие операции:

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", который фактически освобождает зарезервированную память. Чтобы заставить это работать, не существует прямого пути с использованием документированных API QByteArray. Я мог бы найти способ, который можно опробовать.

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, делает буфер пустым. Поэтому дальнейшие призывы к добавлениям законны.

Другие вопросы по тегам