Add QArrayData::reallocateUnaligned for QString and QByteArray

This function reallocates a QArrayData block with realloc() but, as
the name says, it's only valid for types that do not increase the
alignment requirements. I don't think it's worth doing this for types
that do increase the alignment requirements, since we don't know the
alignment of the pointer returned by realloc(). If the new pointer
modulo the alignment is different from the old pointer modulo the
alignment, we'd have to memmove data around, which would be quite
inefficient (realloc might have memcpy'ed already and this memmove
would copy data to nearby).

This function is intended to be used especially in QString and
QByteArray, which were already using realloc() on pointers created by
QArrayData::allocate.

Change-Id: I45b61247db2e84797ad794c1049c47a09c1fb29a
Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
This commit is contained in:
Thiago Macieira 2012-06-05 17:21:19 +02:00
parent 8e5cd8017e
commit 42f974f56b
5 changed files with 105 additions and 36 deletions

View File

@ -63,6 +63,29 @@ QT_WARNING_POP
static const QArrayData &qt_array_empty = qt_array[0];
static const QArrayData &qt_array_unsharable_empty = qt_array[1];
static inline size_t calculateBlockSize(size_t &capacity, size_t objectSize, size_t headerSize,
uint options)
{
// Calculate the byte size
// allocSize = objectSize * capacity + headerSize, but checked for overflow
// plus padded to grow in size
if (options & QArrayData::Grow) {
auto r = qCalculateGrowingBlockSize(capacity, objectSize, headerSize);
capacity = r.elementCount;
return r.size;
} else {
return qCalculateBlockSize(capacity, objectSize, headerSize);
}
}
static QArrayData *reallocateData(QArrayData *header, size_t allocSize, uint options)
{
header = static_cast<QArrayData *>(::realloc(header, allocSize));
if (header)
header->capacityReserved = bool(options & QArrayData::CapacityReserved);
return header;
}
QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
size_t capacity, AllocationOptions options) Q_DECL_NOTHROW
{
@ -91,18 +114,7 @@ QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
if (headerSize > size_t(MaxAllocSize))
return 0;
// Calculate the byte size
// allocSize = objectSize * capacity + headerSize, but checked for overflow
// plus padded to grow in size
size_t allocSize;
if (options & Grow) {
auto r = qCalculateGrowingBlockSize(capacity, objectSize, headerSize);
capacity = r.elementCount;
allocSize = r.size;
} else {
allocSize = qCalculateBlockSize(capacity, objectSize, headerSize);
}
size_t allocSize = calculateBlockSize(capacity, objectSize, headerSize, options);
QArrayData *header = static_cast<QArrayData *>(::malloc(allocSize));
if (header) {
quintptr data = (quintptr(header) + sizeof(QArrayData) + alignment - 1)
@ -122,6 +134,21 @@ QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
return header;
}
QArrayData *QArrayData::reallocateUnaligned(QArrayData *data, size_t objectSize, size_t capacity,
AllocationOptions options) Q_DECL_NOTHROW
{
Q_ASSERT(data);
Q_ASSERT(data->isMutable());
Q_ASSERT(!data->ref.isShared());
size_t headerSize = sizeof(QArrayData);
size_t allocSize = calculateBlockSize(capacity, objectSize, headerSize, options);
QArrayData *header = static_cast<QArrayData *>(reallocateData(data, allocSize, options));
if (header)
header->alloc = capacity;
return header;
}
void QArrayData::deallocate(QArrayData *data, size_t objectSize,
size_t alignment) Q_DECL_NOTHROW
{

View File

@ -115,6 +115,9 @@ struct Q_CORE_EXPORT QArrayData
static QArrayData *allocate(size_t objectSize, size_t alignment,
size_t capacity, AllocationOptions options = Default)
Q_DECL_NOTHROW Q_REQUIRED_RESULT;
static QArrayData *reallocateUnaligned(QArrayData *data, size_t objectSize,
size_t newCapacity, AllocationOptions newOptions = Default)
Q_DECL_NOTHROW Q_REQUIRED_RESULT;
static void deallocate(QArrayData *data, size_t objectSize,
size_t alignment) Q_DECL_NOTHROW;
@ -222,6 +225,14 @@ struct QTypedArrayData
Q_ALIGNOF(AlignmentDummy), capacity, options));
}
static QTypedArrayData *reallocateUnaligned(QTypedArrayData *data, size_t capacity,
AllocationOptions options = Default)
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return static_cast<QTypedArrayData *>(QArrayData::reallocateUnaligned(data, sizeof(T),
capacity, options));
}
static void deallocate(QArrayData *data)
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));

View File

@ -1706,19 +1706,8 @@ void QByteArray::reallocData(uint alloc, Data::AllocationOptions options)
Data::deallocate(d);
d = x;
} else {
size_t blockSize;
if (options & Data::Grow) {
auto r = qCalculateGrowingBlockSize(alloc, sizeof(QChar), sizeof(Data));
blockSize = r.size;
alloc = uint(r.elementCount);
} else {
blockSize = qCalculateBlockSize(alloc, sizeof(QChar), sizeof(Data));
}
Data *x = static_cast<Data *>(::realloc(d, blockSize));
Data *x = Data::reallocateUnaligned(d, alloc, options);
Q_CHECK_PTR(x);
x->alloc = alloc;
x->capacityReserved = (options & Data::CapacityReserved) ? 1 : 0;
d = x;
}
}

View File

@ -1758,17 +1758,11 @@ void QString::resize(int size, QChar fillChar)
void QString::reallocData(uint alloc, bool grow)
{
size_t blockSize;
if (grow) {
auto r = qCalculateGrowingBlockSize(alloc, sizeof(QChar), sizeof(Data));
blockSize = r.size;
alloc = uint(r.elementCount);
} else {
blockSize = qCalculateBlockSize(alloc, sizeof(QChar), sizeof(Data));
}
auto allocOptions = d->detachFlags();
if (grow)
allocOptions |= QArrayData::Grow;
if (d->ref.isShared() || IS_RAW_DATA(d)) {
Data::AllocationOptions allocOptions(d->capacityReserved ? Data::CapacityReserved : 0);
Data *x = Data::allocate(alloc, allocOptions);
Q_CHECK_PTR(x);
x->size = qMin(int(alloc) - 1, d->size);
@ -1778,11 +1772,9 @@ void QString::reallocData(uint alloc, bool grow)
Data::deallocate(d);
d = x;
} else {
Data *p = static_cast<Data *>(::realloc(d, blockSize));
Data *p = Data::reallocateUnaligned(d, alloc, allocOptions);
Q_CHECK_PTR(p);
d = p;
d->alloc = alloc;
d->offset = sizeof(QStringData);
}
}

View File

@ -65,6 +65,8 @@ private slots:
void simpleVectorReserve();
void allocate_data();
void allocate();
void reallocate_data() { allocate_data(); }
void reallocate();
void alignment_data();
void alignment();
void typedData();
@ -742,6 +744,54 @@ void tst_QArrayData::allocate()
}
}
void tst_QArrayData::reallocate()
{
QFETCH(size_t, objectSize);
QFETCH(size_t, alignment);
QFETCH(QArrayData::AllocationOptions, allocateOptions);
QFETCH(bool, isCapacityReserved);
QFETCH(const QArrayData *, commonEmpty);
// Maximum alignment that can be requested is that of QArrayData,
// otherwise, we can't use reallocate().
Q_ASSERT(alignment <= Q_ALIGNOF(QArrayData));
// Minimum alignment that can be requested is that of QArrayData.
// Typically, this alignment is sizeof(void *) and ensured by malloc.
size_t minAlignment = qMax(alignment, Q_ALIGNOF(QArrayData));
int capacity = 10;
Deallocator keeper(objectSize, minAlignment);
QArrayData *data = QArrayData::allocate(objectSize, minAlignment, capacity,
QArrayData::AllocationOptions(allocateOptions) & ~QArrayData::Grow);
keeper.headers.append(data);
memset(data->data(), 'A', objectSize * capacity);
data->size = capacity;
// now try to reallocate
int newCapacity = 40;
data = QArrayData::reallocateUnaligned(data, objectSize, newCapacity,
QArrayData::AllocationOptions(allocateOptions));
QVERIFY(data);
keeper.headers.clear();
keeper.headers.append(data);
QCOMPARE(data->size, capacity);
if (allocateOptions & QArrayData::Grow)
QVERIFY(data->alloc > uint(newCapacity));
else
QCOMPARE(data->alloc, uint(newCapacity));
QCOMPARE(data->capacityReserved, uint(isCapacityReserved));
#if !defined(QT_NO_UNSHARABLE_CONTAINERS)
QFETCH(bool, isSharable);
QCOMPARE(data->ref.isSharable(), isSharable);
#endif
for (int i = 0; i < capacity; ++i)
QCOMPARE(static_cast<char *>(data->data())[i], 'A');
}
class Unaligned
{
char dummy[8];