Add setSharable support in QArrayData stack

Making use of the same feature added in RefCount.

To keep with the intention of avoiding the allocation of "empty" array
headers, this introduces an unsharable_empty, which allows users to
maintain the "unsharable bit" on empty containers, without imposing any
actual allocations.

(Before anyone asks, there is no point to a zero-sized capacity-reserved
container so no other combinations are needed for now.)

Change-Id: Icaa40ac3100ad954fdc20dee0c991861136a5b19
Reviewed-by: Bradley T. Hughes <bradley.hughes@nokia.com>
This commit is contained in:
João Abecasis 2011-11-24 17:15:11 +01:00 committed by Qt by Nokia
parent 51048e1f31
commit 3d61c5ca8f
6 changed files with 193 additions and 21 deletions

View File

@ -45,9 +45,10 @@ QT_BEGIN_NAMESPACE
const QArrayData QArrayData::shared_null = { Q_REFCOUNT_INITIALIZE_STATIC, 0, 0, 0, 0 };
const QArrayData QArrayData::shared_empty = { Q_REFCOUNT_INITIALIZE_STATIC, 0, 0, 0, 0 };
const QArrayData QArrayData::unsharable_empty = { { Q_BASIC_ATOMIC_INITIALIZER(0) }, 0, 0, 0, 0 };
QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
size_t capacity, bool reserve)
size_t capacity, bool reserve, bool sharable)
{
// Alignment is a power of two
Q_ASSERT(alignment >= Q_ALIGNOF(QArrayData)
@ -55,7 +56,9 @@ QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
// Don't allocate empty headers
if (!capacity)
return const_cast<QArrayData *>(&shared_empty);
return sharable
? const_cast<QArrayData *>(&shared_empty)
: const_cast<QArrayData *>(&unsharable_empty);
// Allocate extra (alignment - Q_ALIGNOF(QArrayData)) padding bytes so we
// can properly align the data array. This assumes malloc is able to
@ -69,7 +72,7 @@ QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
quintptr data = (quintptr(header) + sizeof(QArrayData) + alignment - 1)
& ~(alignment - 1);
header->ref.initializeOwned();
header->ref.atomic.store(sharable ? 1 : 0);
header->size = 0;
header->alloc = capacity;
header->capacityReserved = reserve;
@ -87,6 +90,9 @@ void QArrayData::deallocate(QArrayData *data, size_t objectSize,
&& !(alignment & (alignment - 1)));
Q_UNUSED(objectSize) Q_UNUSED(alignment)
if (data == &unsharable_empty)
return;
qFree(data);
}

View File

@ -74,12 +74,13 @@ struct Q_CORE_EXPORT QArrayData
}
static QArrayData *allocate(size_t objectSize, size_t alignment,
size_t capacity, bool reserve) Q_REQUIRED_RESULT;
size_t capacity, bool reserve, bool sharable) Q_REQUIRED_RESULT;
static void deallocate(QArrayData *data, size_t objectSize,
size_t alignment);
static const QArrayData shared_null;
static const QArrayData shared_empty;
static const QArrayData unsharable_empty;
};
template <class T>
@ -99,11 +100,11 @@ struct QTypedArrayData
class AlignmentDummy { QArrayData header; T data; };
static QTypedArrayData *allocate(size_t capacity, bool reserve = false)
Q_REQUIRED_RESULT
static QTypedArrayData *allocate(size_t capacity, bool reserve = false,
bool sharable = true) Q_REQUIRED_RESULT
{
return static_cast<QTypedArrayData *>(QArrayData::allocate(sizeof(T),
Q_ALIGNOF(AlignmentDummy), capacity, reserve));
Q_ALIGNOF(AlignmentDummy), capacity, reserve, sharable));
}
static void deallocate(QArrayData *data)
@ -122,6 +123,12 @@ struct QTypedArrayData
return static_cast<QTypedArrayData *>(
const_cast<QArrayData *>(&QArrayData::shared_empty));
}
static QTypedArrayData *unsharableEmpty()
{
return static_cast<QTypedArrayData *>(
const_cast<QArrayData *>(&QArrayData::unsharable_empty));
}
};
template <class T, size_t N>

View File

@ -61,7 +61,7 @@ struct QPodArrayOps
{
void copyAppend(const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(b < e);
Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size));
@ -71,7 +71,7 @@ struct QPodArrayOps
void copyAppend(size_t n, const T &t)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(n <= this->alloc - uint(this->size));
T *iter = this->end();
@ -91,7 +91,7 @@ struct QPodArrayOps
void insert(T *where, const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end()); // No overlap
@ -109,7 +109,7 @@ struct QGenericArrayOps
{
void copyAppend(const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(b < e);
Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size));
@ -122,7 +122,7 @@ struct QGenericArrayOps
void copyAppend(size_t n, const T &t)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(n <= this->alloc - uint(this->size));
T *iter = this->end();
@ -149,7 +149,7 @@ struct QGenericArrayOps
void insert(T *where, const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end()); // No overlap
@ -222,7 +222,7 @@ struct QMovableArrayOps
void insert(T *where, const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(!this->ref.isShared());
Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end()); // No overlap

View File

@ -64,7 +64,9 @@ public:
}
QArrayDataPointer(const QArrayDataPointer &other)
: d((other.d->ref.ref(), other.d))
: d(other.d->ref.ref()
? other.d
: other.clone())
{
}
@ -110,6 +112,22 @@ public:
return d;
}
void setSharable(bool sharable)
{
if (d->alloc == 0 && d->size == 0) {
Q_ASSERT(Data::sharedNull() == d
|| Data::sharedEmpty() == d
|| Data::unsharableEmpty() == d);
d = sharable
? Data::sharedEmpty()
: Data::unsharableEmpty();
return;
}
detach();
d->ref.setSharable(sharable);
}
void swap(QArrayDataPointer &other)
{
qSwap(d, other.d);

View File

@ -111,6 +111,7 @@ public:
{ return atomic.load(); }
void initializeOwned() { atomic.store(1); }
void initializeUnsharable() { atomic.store(0); }
QBasicAtomicInt atomic;
};

View File

@ -79,6 +79,8 @@ private slots:
void typedData();
void gccBug43247();
void arrayOps();
void setSharable_data();
void setSharable();
};
template <class T> const T &const_(const T &t) { return t; }
@ -477,6 +479,7 @@ void tst_QArrayData::allocate_data()
QTest::addColumn<size_t>("objectSize");
QTest::addColumn<size_t>("alignment");
QTest::addColumn<bool>("isCapacityReserved");
QTest::addColumn<bool>("isSharable");
QTest::addColumn<const QArrayData *>("commonEmpty");
struct {
@ -492,10 +495,13 @@ void tst_QArrayData::allocate_data()
struct {
char const *description;
bool isCapacityReserved;
bool isSharable;
const QArrayData *commonEmpty;
} options[] = {
{ "Default", false, &QArrayData::shared_empty },
{ "Reserved", true, &QArrayData::shared_empty },
{ "Default", false, true, &QArrayData::shared_empty },
{ "Reserved", true, true, &QArrayData::shared_empty },
{ "Reserved | Unsharable", true, false, &QArrayData::unsharable_empty },
{ "Unsharable", false, false, &QArrayData::unsharable_empty },
};
for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); ++i)
@ -505,7 +511,8 @@ void tst_QArrayData::allocate_data()
+ QLatin1String(": ")
+ QLatin1String(options[j].description)))
<< types[i].objectSize << types[i].alignment
<< options[j].isCapacityReserved << options[j].commonEmpty;
<< options[j].isCapacityReserved << options[j].isSharable
<< options[j].commonEmpty;
}
void tst_QArrayData::allocate()
@ -513,6 +520,7 @@ void tst_QArrayData::allocate()
QFETCH(size_t, objectSize);
QFETCH(size_t, alignment);
QFETCH(bool, isCapacityReserved);
QFETCH(bool, isSharable);
QFETCH(const QArrayData *, commonEmpty);
// Minimum alignment that can be requested is that of QArrayData.
@ -521,19 +529,20 @@ void tst_QArrayData::allocate()
// Shared Empty
QCOMPARE(QArrayData::allocate(objectSize, minAlignment, 0,
isCapacityReserved), commonEmpty);
isCapacityReserved, isSharable), commonEmpty);
Deallocator keeper(objectSize, minAlignment);
keeper.headers.reserve(1024);
for (int capacity = 1; capacity <= 1024; capacity <<= 1) {
QArrayData *data = QArrayData::allocate(objectSize, minAlignment,
capacity, isCapacityReserved);
capacity, isCapacityReserved, isSharable);
keeper.headers.append(data);
QCOMPARE(data->size, 0);
QVERIFY(data->alloc >= uint(capacity));
QCOMPARE(data->capacityReserved, uint(isCapacityReserved));
QCOMPARE(data->ref.isSharable(), isSharable);
// Check that the allocated array can be used. Best tested with a
// memory checker, such as valgrind, running.
@ -569,7 +578,7 @@ void tst_QArrayData::alignment()
for (int i = 0; i < 100; ++i) {
QArrayData *data = QArrayData::allocate(sizeof(Unaligned),
minAlignment, 8, false);
minAlignment, 8, false, true);
keeper.headers.append(data);
QVERIFY(data);
@ -903,5 +912,136 @@ void tst_QArrayData::arrayOps()
}
}
Q_DECLARE_METATYPE(QArrayDataPointer<int>)
static inline bool arrayIsFilledWith(const QArrayDataPointer<int> &array,
int fillValue, size_t size)
{
const int *iter = array->begin();
const int *const end = array->end();
for (size_t i = 0; i < size; ++i, ++iter)
if (*iter != fillValue)
return false;
if (iter != end)
return false;
return true;
}
void tst_QArrayData::setSharable_data()
{
QTest::addColumn<QArrayDataPointer<int> >("array");
QTest::addColumn<size_t>("size");
QTest::addColumn<size_t>("capacity");
QTest::addColumn<bool>("isCapacityReserved");
QTest::addColumn<int>("fillValue");
QArrayDataPointer<int> null;
QArrayDataPointer<int> empty; empty.clear();
static QStaticArrayData<int, 10> staticArrayData = {
Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER(int, 10),
{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }
};
QArrayDataPointer<int> emptyReserved(QTypedArrayData<int>::allocate(5, true, true));
QArrayDataPointer<int> nonEmpty(QTypedArrayData<int>::allocate(10, false, true));
QArrayDataPointer<int> nonEmptyReserved(QTypedArrayData<int>::allocate(15, true, true));
QArrayDataPointer<int> staticArray(static_cast<QTypedArrayData<int> *>(&staticArrayData.header));
nonEmpty->copyAppend(5, 1);
nonEmptyReserved->copyAppend(7, 2);
QTest::newRow("shared-null") << null << size_t(0) << size_t(0) << false << 0;
QTest::newRow("shared-empty") << empty << size_t(0) << size_t(0) << false << 0;
// unsharable-empty implicitly tested in shared-empty
QTest::newRow("empty-reserved") << emptyReserved << size_t(0) << size_t(5) << true << 0;
QTest::newRow("non-empty") << nonEmpty << size_t(5) << size_t(10) << false << 1;
QTest::newRow("non-empty-reserved") << nonEmptyReserved << size_t(7) << size_t(15) << true << 2;
QTest::newRow("static-array") << staticArray << size_t(10) << size_t(0) << false << 3;
}
void tst_QArrayData::setSharable()
{
QFETCH(QArrayDataPointer<int>, array);
QFETCH(size_t, size);
QFETCH(size_t, capacity);
QFETCH(bool, isCapacityReserved);
QFETCH(int, fillValue);
QVERIFY(array->ref.isShared()); // QTest has a copy
QVERIFY(array->ref.isSharable());
QCOMPARE(size_t(array->size), size);
QCOMPARE(size_t(array->alloc), capacity);
QCOMPARE(bool(array->capacityReserved), isCapacityReserved);
QVERIFY(arrayIsFilledWith(array, fillValue, size));
// shared-null becomes shared-empty, may otherwise detach
array.setSharable(true);
QVERIFY(array->ref.isSharable());
QVERIFY(arrayIsFilledWith(array, fillValue, size));
{
QArrayDataPointer<int> copy(array);
QVERIFY(array->ref.isShared());
QVERIFY(array->ref.isSharable());
QCOMPARE(copy.data(), array.data());
}
// Unshare, must detach
array.setSharable(false);
// Immutability (alloc == 0) is lost on detach
if (capacity == 0 && size != 0)
capacity = size;
QVERIFY(!array->ref.isShared());
QVERIFY(!array->ref.isSharable());
QCOMPARE(size_t(array->size), size);
QCOMPARE(size_t(array->alloc), capacity);
QCOMPARE(bool(array->capacityReserved), isCapacityReserved);
QVERIFY(arrayIsFilledWith(array, fillValue, size));
{
QArrayDataPointer<int> copy(array);
QVERIFY(!array->ref.isShared());
QVERIFY(!array->ref.isSharable());
// Null/empty is always shared
QCOMPARE(copy->ref.isShared(), !(size || isCapacityReserved));
QVERIFY(copy->ref.isSharable());
QCOMPARE(size_t(copy->size), size);
QCOMPARE(size_t(copy->alloc), capacity);
QCOMPARE(bool(copy->capacityReserved), isCapacityReserved);
QVERIFY(arrayIsFilledWith(copy, fillValue, size));
}
// Make sharable, again
array.setSharable(true);
QCOMPARE(array->ref.isShared(), !(size || isCapacityReserved));
QVERIFY(array->ref.isSharable());
QCOMPARE(size_t(array->size), size);
QCOMPARE(size_t(array->alloc), capacity);
QCOMPARE(bool(array->capacityReserved), isCapacityReserved);
QVERIFY(arrayIsFilledWith(array, fillValue, size));
{
QArrayDataPointer<int> copy(array);
QVERIFY(array->ref.isShared());
QCOMPARE(copy.data(), array.data());
}
QCOMPARE(array->ref.isShared(), !(size || isCapacityReserved));
QVERIFY(array->ref.isSharable());
}
QTEST_APPLESS_MAIN(tst_QArrayData)
#include "tst_qarraydata.moc"