Reduce foldCasing of the needle in Boyer-Moore QString searches

Before searching, foldCase the first up to 256 characters, and use this
buffer to compare against the haystack. If the needle is larger than the
buffer, compare the rest of the needle against the rest of the haystack
for every potential match. The buffer is placed on the stack and must be
refolded for each search, but this change does not break the API.

This is faster than the old implementation, except if the needle is long
and it is found near the beginning of the haystack, or if the needle is
long and it is not found in a short haystack where few comparisons are
done and hence few case foldings were needed in the old implementation.

Benchmarking using tst_bench_qstringtokenizer tokenize_qstring_qstring
shows an improvement for the the total testcase and usually for each
individual test.

Fixes: QTBUG-100239
Change-Id: Ie61342eb5c19f32de3c1ba0a51dbb0db503bdf3a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Øystein Heskestad 2022-09-20 17:48:11 +02:00
parent 266eb124a2
commit d8387fc538
2 changed files with 55 additions and 11 deletions

View File

@ -6,10 +6,13 @@
QT_BEGIN_NAMESPACE
static constexpr qsizetype FoldBufferCapacity = 256;
static void bm_init_skiptable(QStringView needle, uchar *skiptable, Qt::CaseSensitivity cs)
{
const char16_t *uc = needle.utf16();
const qsizetype len = needle.size();
const qsizetype len =
cs == Qt::CaseSensitive ? needle.size() : qMin(needle.size(), FoldBufferCapacity);
qsizetype l = qMin(len, qsizetype(255));
memset(skiptable, l, 256 * sizeof(uchar));
uc += len - l;
@ -37,11 +40,12 @@ static inline qsizetype bm_find(QStringView haystack, qsizetype index, QStringVi
if (pl == 0)
return index > l ? -1 : index;
const qsizetype pl_minus_one = pl - 1;
const char16_t *current = uc + index + pl_minus_one;
const char16_t *end = uc + l;
if (cs == Qt::CaseSensitive) {
const qsizetype pl_minus_one = pl - 1;
const char16_t *current = uc + index + pl_minus_one;
const char16_t *end = uc + l;
while (current < end) {
qsizetype skip = skiptable[*current & 0xff];
if (!skip) {
@ -66,21 +70,38 @@ static inline qsizetype bm_find(QStringView haystack, qsizetype index, QStringVi
current += skip;
}
} else {
char16_t foldBuffer[FoldBufferCapacity];
const qsizetype foldBufferLength = qMin(FoldBufferCapacity, pl);
const char16_t *start = puc;
for (qsizetype i = 0; i < foldBufferLength; ++i)
foldBuffer[i] = foldCase(&puc[i], start);
QStringView restNeedle = needle.sliced(foldBufferLength);
const qsizetype foldBufferEnd = foldBufferLength - 1;
const char16_t *current = uc + index + foldBufferEnd;
const char16_t *end = uc + l;
while (current < end) {
qsizetype skip = skiptable[foldCase(current, uc) & 0xff];
if (!skip) {
// possible match
while (skip < pl) {
if (foldCase(current - skip, uc) != foldCase(puc + pl_minus_one - skip, puc))
while (skip < foldBufferLength) {
if (foldCase(current - skip, uc) != foldBuffer[foldBufferEnd - skip])
break;
++skip;
}
if (skip > pl_minus_one) // we have a match
return (current - uc) - pl_minus_one;
if (skip > foldBufferEnd) { // Matching foldBuffer
qsizetype candidatePos = (current - uc) - foldBufferEnd;
QStringView restHaystack =
haystack.sliced(qMin(haystack.size(), candidatePos + foldBufferLength));
if (restNeedle.size() == 0
|| restHaystack.startsWith(
restNeedle, Qt::CaseInsensitive)) // Check the rest of the string
return candidatePos;
}
// in case we don't have a match we are a bit inefficient as we only skip by one
// when we have the non matching char in the string.
if (skiptable[foldCase(current - skip, uc) & 0xff] == pl)
skip = pl - skip;
if (skiptable[foldCase(current - skip, uc) & 0xff] == foldBufferLength)
skip = foldBufferLength - skip;
else
skip = 1;
}

View File

@ -92,7 +92,30 @@ void tst_QStringMatcher::setCaseSensitivity_data()
QTest::newRow("overshot") << QString("foo") << QString("baFooz foo bar") << 14 << -1 << (int) Qt::CaseSensitive;
QTest::newRow("sensitive") << QString("foo") << QString("baFooz foo bar") << 1 << 7 << (int) Qt::CaseSensitive;
QTest::newRow("insensitive") << QString("foo") << QString("baFooz foo bar") << 1 << 2 << (int) Qt::CaseInsensitive;
QTest::newRow("insensitive-1")
<< QString("foo") << QString("baFooz foo bar") << 0 << 2 << (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-2")
<< QString("foo") << QString("baFooz foo bar") << 1 << 2 << (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-3")
<< QString("foo") << QString("baFooz foo bar") << 4 << 7 << (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-4")
<< QString("foogabooga") << QString("baFooGaBooga foogabooga bar") << 1 << 2
<< (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-5")
<< QString("foogabooga") << QString("baFooGaBooga foogabooga bar") << 3 << 13
<< (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-6") << QString("foogabooga") << QString("GaBoogaFoogaBooga bar") << 0
<< 7 << (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-7") << QString("foogabooga") << QString("FoGaBoogaFoogaBooga") << 9
<< 9 << (int)Qt::CaseInsensitive;
QTest::newRow("insensitive-8") << QString("foogaBooga") << QString("zzzzaazzffoogaBooga") << 0
<< 9 << (int)Qt::CaseInsensitive;
QString stringOf32("abcdefghijklmnopqrstuvwxyz123456");
Q_ASSERT(stringOf32.size() == 32);
QString stringOf128 = stringOf32 + stringOf32 + stringOf32 + stringOf32;
QString needle = stringOf128 + stringOf128 + "CAse";
QString haystack = stringOf128 + stringOf128 + "caSE";
QTest::newRow("insensitive-9") << needle << haystack << 0 << 0 << (int)Qt::CaseInsensitive;
}
void tst_QStringMatcher::setCaseSensitivity()