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