QBitArray: add mutation operators taking rvalue QBitArrays

Because we may be able to use the other side's storage and apply the
operation in-place. We reuse the storage of the one that can be
detached: even if we have to grow the buffer, QBitArrays are usually
small so there's a good chance it's just to the extra space QArrayData
usually overallocates or a simple, non-moving realloc().

Disassembly with GCC 13 and Clang 17 show the vectorisers did kick in
for all four functions (inverted_inplace included). I think a
hand-rolled version could squeeze a few more cycles, especially in the
tail section, but I don't consider its maintenance cost to be worth it.

Change-Id: I85b3fc2dd45c4693be13fffd1795bfb1b296caa6
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
(cherry picked from commit 948fa2a427dbcdc2784abdcf87efc914417a0028)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Thiago Macieira 2023-11-08 12:26:44 -08:00 committed by Qt Cherry-pick Bot
parent 8247adeaed
commit b9268bef71
3 changed files with 192 additions and 30 deletions

View File

@ -562,7 +562,68 @@ QBitArray &performBitwiseOperationHelper(QBitArray &out, const QBitArray &a1,
return out; return out;
} }
template <typename BitwiseOp> static Q_NEVER_INLINE
QBitArray &performBitwiseOperationInCopy(QBitArray &self, const QBitArray &other, BitwiseOp op)
{
QBitArray tmp(std::move(self));
self = sizedForOverwrite(tmp, other);
return performBitwiseOperationHelper(self, tmp, other, op);
}
template <typename BitwiseOp> static Q_NEVER_INLINE
QBitArray &performBitwiseOperationInPlace(QBitArray &self, const QBitArray &other, BitwiseOp op)
{
if (self.size() < other.size())
self.resize(other.size());
return performBitwiseOperationHelper(self, self, other, op);
}
template <typename BitwiseOp> static
QBitArray &performBitwiseOperation(QBitArray &self, const QBitArray &other, BitwiseOp op)
{
if (self.data_ptr().needsDetach())
return performBitwiseOperationInCopy(self, other, op);
return performBitwiseOperationInPlace(self, other, op);
}
// SCARY helper
enum { InCopy, InPlace };
static auto prepareForBitwiseOperation(QBitArray &self, QBitArray &other)
{
QByteArrayData &d1 = self.data_ptr();
QByteArrayData &d2 = other.data_ptr();
bool detached1 = !d1.needsDetach();
bool detached2 = !d2.needsDetach();
if (!detached1 && !detached2)
return InCopy;
// at least one of the two is detached, we'll reuse its buffer
bool swap = false;
if (detached1 && detached2) {
// both are detached, so choose the larger of the two
swap = d1.allocatedCapacity() < d2.allocatedCapacity();
} else if (detached2) {
// we can re-use other's buffer but not self's, so swap the two
swap = true;
}
if (swap)
self.swap(other);
return InPlace;
}
template <typename BitwiseOp> static
QBitArray &performBitwiseOperation(QBitArray &self, QBitArray &other, BitwiseOp op)
{
auto choice = prepareForBitwiseOperation(self, other);
if (choice == InCopy)
return performBitwiseOperationInCopy(self, other, std::move(op));
return performBitwiseOperationInPlace(self, other, std::move(op));
}
/*! /*!
\fn QBitArray &QBitArray::operator&=(const QBitArray &other)
\fn QBitArray &QBitArray::operator&=(QBitArray &&other)
Performs the AND operation between all bits in this bit array and Performs the AND operation between all bits in this bit array and
\a other. Assigns the result to this bit array, and returns a \a other. Assigns the result to this bit array, and returns a
reference to it. reference to it.
@ -577,21 +638,20 @@ QBitArray &performBitwiseOperationHelper(QBitArray &out, const QBitArray &a1,
\sa operator&(), operator|=(), operator^=(), operator~() \sa operator&(), operator|=(), operator^=(), operator~()
*/ */
QBitArray &QBitArray::operator&=(QBitArray &&other)
{
return performBitwiseOperation(*this, other, std::bit_and<uchar>());
}
QBitArray &QBitArray::operator&=(const QBitArray &other) QBitArray &QBitArray::operator&=(const QBitArray &other)
{ {
resize(qMax(size(), other.size())); return performBitwiseOperation(*this, other, std::bit_and<uchar>());
uchar *a1 = reinterpret_cast<uchar *>(d.data()) + 1;
const uchar *a2 = reinterpret_cast<const uchar *>(other.d.constData()) + 1;
qsizetype n = other.d.size() - 1;
qsizetype p = d.size() - 1 - n;
while (n-- > 0)
*a1++ &= *a2++;
while (p-- > 0)
*a1++ = 0;
return *this;
} }
/*! /*!
\fn QBitArray &QBitArray::operator|=(const QBitArray &other)
\fn QBitArray &QBitArray::operator|=(QBitArray &&other)
Performs the OR operation between all bits in this bit array and Performs the OR operation between all bits in this bit array and
\a other. Assigns the result to this bit array, and returns a \a other. Assigns the result to this bit array, and returns a
reference to it. reference to it.
@ -606,18 +666,20 @@ QBitArray &QBitArray::operator&=(const QBitArray &other)
\sa operator|(), operator&=(), operator^=(), operator~() \sa operator|(), operator&=(), operator^=(), operator~()
*/ */
QBitArray &QBitArray::operator|=(QBitArray &&other)
{
return performBitwiseOperation(*this, other, std::bit_or<uchar>());
}
QBitArray &QBitArray::operator|=(const QBitArray &other) QBitArray &QBitArray::operator|=(const QBitArray &other)
{ {
resize(qMax(size(), other.size())); return performBitwiseOperation(*this, other, std::bit_or<uchar>());
uchar *a1 = reinterpret_cast<uchar *>(d.data()) + 1;
const uchar *a2 = reinterpret_cast<const uchar *>(other.d.constData()) + 1;
qsizetype n = other.d.size() - 1;
while (n-- > 0)
*a1++ |= *a2++;
return *this;
} }
/*! /*!
\fn QBitArray &QBitArray::operator^=(const QBitArray &other)
\fn QBitArray &QBitArray::operator^=(QBitArray &&other)
Performs the XOR operation between all bits in this bit array and Performs the XOR operation between all bits in this bit array and
\a other. Assigns the result to this bit array, and returns a \a other. Assigns the result to this bit array, and returns a
reference to it. reference to it.
@ -632,15 +694,14 @@ QBitArray &QBitArray::operator|=(const QBitArray &other)
\sa operator^(), operator&=(), operator|=(), operator~() \sa operator^(), operator&=(), operator|=(), operator~()
*/ */
QBitArray &QBitArray::operator^=(QBitArray &&other)
{
return performBitwiseOperation(*this, other, std::bit_xor<uchar>());
}
QBitArray &QBitArray::operator^=(const QBitArray &other) QBitArray &QBitArray::operator^=(const QBitArray &other)
{ {
resize(qMax(size(), other.size())); return performBitwiseOperation(*this, other, std::bit_xor<uchar>());
uchar *a1 = reinterpret_cast<uchar *>(d.data()) + 1;
const uchar *a2 = reinterpret_cast<const uchar *>(other.d.constData()) + 1;
qsizetype n = other.d.size() - 1;
while (n-- > 0)
*a1++ ^= *a2++;
return *this;
} }
/*! /*!

View File

@ -103,6 +103,9 @@ public:
QBitRef operator[](qsizetype i); QBitRef operator[](qsizetype i);
bool operator[](qsizetype i) const { return testBit(i); } bool operator[](qsizetype i) const { return testBit(i); }
QBitArray &operator&=(QBitArray &&);
QBitArray &operator|=(QBitArray &&);
QBitArray &operator^=(QBitArray &&);
QBitArray &operator&=(const QBitArray &); QBitArray &operator&=(const QBitArray &);
QBitArray &operator|=(const QBitArray &); QBitArray &operator|=(const QBitArray &);
QBitArray &operator^=(const QBitArray &); QBitArray &operator^=(const QBitArray &);

View File

@ -316,9 +316,34 @@ void tst_QBitArray::operator_andeq()
QFETCH(QBitArray, input2); QFETCH(QBitArray, input2);
QFETCH(QBitArray, res); QFETCH(QBitArray, res);
input1&=input2; QBitArray result = input1;
result &= input2;
QCOMPARE(result, res);
result = input1;
result &= std::move(input2);
QCOMPARE(result, res);
result = input1;
result &= detached(input2);
QCOMPARE(result, res);
QCOMPARE(input1, res); // operation is commutative
result = input2;
result &= input1;
QCOMPARE(result, res);
result = input2;
result &= std::move(input1);
QCOMPARE(result, res);
result = input2;
result &= detached(input1);
QCOMPARE(result, res);
// operation is idempotent
result &= result;
QCOMPARE(result, res);
result &= std::move(result);
QCOMPARE(result, res);
result &= detached(result);
QCOMPARE(result, res);
} }
void tst_QBitArray::operator_and() void tst_QBitArray::operator_and()
@ -397,9 +422,34 @@ void tst_QBitArray::operator_oreq()
QFETCH(QBitArray, input2); QFETCH(QBitArray, input2);
QFETCH(QBitArray, res); QFETCH(QBitArray, res);
input1|=input2; QBitArray result = input1;
result |= input2;
QCOMPARE(result, res);
result = input1;
result |= QBitArray(input2);
QCOMPARE(result, res);
result = input1;
result |= detached(input2);
QCOMPARE(result, res);
QCOMPARE(input1, res); // operation is commutative
result = input2;
result |= input1;
QCOMPARE(result, res);
result = input2;
result |= QBitArray(input1);
QCOMPARE(result, res);
result = input2;
result |= detached(input1);
QCOMPARE(result, res);
// operation is idempotent
result |= result;
QCOMPARE(result, res);
result |= QBitArray(result);
QCOMPARE(result, res);
result |= detached(result);
QCOMPARE(result, res);
} }
void tst_QBitArray::operator_or() void tst_QBitArray::operator_or()
@ -476,9 +526,57 @@ void tst_QBitArray::operator_xoreq()
QFETCH(QBitArray, input2); QFETCH(QBitArray, input2);
QFETCH(QBitArray, res); QFETCH(QBitArray, res);
input1^=input2; QBitArray result = input1;
result ^= input2;
QCOMPARE(result, res);
result = input1;
result ^= QBitArray(input2);
QCOMPARE(result, res);
result = input1;
result ^= detached(input2);
QCOMPARE(result, res);
QCOMPARE(input1, res); // operation is commutative
result = input2;
result ^= input1;
QCOMPARE(result, res);
result = input2;
result ^= QBitArray(input1);
QCOMPARE(result, res);
result = input2;
result ^= detached(input1);
QCOMPARE(result, res);
// XORing with oneself is nilpotent
result = input1;
result ^= input1;
QCOMPARE(result, QBitArray(input1.size()));
result = input1;
result ^= QBitArray(result);
QCOMPARE(result, QBitArray(input1.size()));
result = input1;
result ^= detached(result);
QCOMPARE(result, QBitArray(input1.size()));
result = input2;
result ^= input2;
QCOMPARE(result, QBitArray(input2.size()));
result = input2;
result ^= QBitArray(input2);
QCOMPARE(result, QBitArray(input2.size()));
result = input2;
result ^= detached(input2);
QCOMPARE(result, QBitArray(input2.size()));
result = res;
result ^= res;
QCOMPARE(result, QBitArray(res.size()));
result = res;
result ^= QBitArray(res);
QCOMPARE(result, QBitArray(res.size()));
result = res;
result ^= detached(res);
QCOMPARE(result, QBitArray(res.size()));
} }
void tst_QBitArray::operator_xor() void tst_QBitArray::operator_xor()