Skip to content

Commit

Permalink
Add QArrayData::reallocateUnaligned for QString and QByteArray
Browse files Browse the repository at this point in the history
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) <[email protected]>
  • Loading branch information
thiagomacieira committed Sep 7, 2016
1 parent 8e5cd80 commit 42f974f
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 36 deletions.
51 changes: 39 additions & 12 deletions src/corelib/tools/qarraydata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand All @@ -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
{
Expand Down
11 changes: 11 additions & 0 deletions src/corelib/tools/qarraydata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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));
Expand Down
13 changes: 1 addition & 12 deletions src/corelib/tools/qbytearray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
16 changes: 4 additions & 12 deletions src/corelib/tools/qstring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}

Expand Down
50 changes: 50 additions & 0 deletions tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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];
Expand Down

0 comments on commit 42f974f

Please sign in to comment.