perf: TypeSelector — zero-alloc fuzzy scorer, warm popup 75% faster

Stack arrays + pre-lowered QChars in fuzzyScore eliminate all heap
allocations in the hot path. applyFilter uses indices instead of
deep-copying TypeEntry. popup() width estimated from cached max name
length. QListView: uniform sizes, batched layout, cached sizeHint.

Benchmark (5000 structs): warm popup 27ms→7ms, filter 5ms→1.7ms.
This commit is contained in:
IChooseYou
2026-03-08 08:33:21 -06:00
committed by IChooseYou
parent 43365c1aff
commit 431e2b90c9
3 changed files with 171 additions and 32 deletions

View File

@@ -322,6 +322,104 @@ private slots:
}
}
// ── Benchmark: large SDK (5000 structs) ──
void benchmarkLargeSDK() {
auto ms = [](qint64 ns) { return QString::number(ns / 1000000.0, 'f', 2); };
// Build 5000 composite types with field summaries (simulates WinSDK)
QVector<TypeEntry> types;
types.reserve(5000);
for (int i = 0; i < 5000; i++) {
TypeEntry e;
e.entryKind = TypeEntry::Composite;
e.structId = (uint64_t)(i + 1);
e.displayName = QStringLiteral("_STRUCT_%1").arg(i, 4, 10, QChar('0'));
e.classKeyword = QStringLiteral("struct");
e.sizeBytes = 64 + (i % 256) * 8;
e.alignment = 8;
e.fieldCount = 5 + (i % 20);
for (int f = 0; f < qMin(6, e.fieldCount); f++)
e.fieldSummary << QStringLiteral("0x%1: int32_t field_%2")
.arg(f * 4, 2, 16, QChar('0')).arg(f);
types.append(e);
}
QFont font("Consolas", 12);
font.setFixedPitch(true);
auto* popup = new TypeSelectorPopup();
popup->warmUp();
popup->setFont(font);
// Measure setTypes (data loading)
QElapsedTimer t;
t.start();
popup->setTypes(types, nullptr);
qint64 tSetTypes = t.nsecsElapsed();
// Measure popup show (broken down)
t.restart();
popup->popup(QPoint(100, 100));
qint64 tPopupCall = t.nsecsElapsed();
t.restart();
QApplication::processEvents();
qint64 tProcessEvents = t.nsecsElapsed();
qint64 tShow = tPopupCall + tProcessEvents;
// Second popup show (warm)
popup->hide();
QApplication::processEvents();
t.restart();
popup->popup(QPoint(100, 100));
qint64 tPopup2 = t.nsecsElapsed();
t.restart();
QApplication::processEvents();
qint64 tProcess2 = t.nsecsElapsed();
// Measure filter with 1-char (worst case: most matches)
t.restart();
auto* filterEdit = popup->findChild<QLineEdit*>();
QVERIFY(filterEdit);
filterEdit->setText(QStringLiteral("S"));
qint64 tFilter1 = t.nsecsElapsed();
// Measure filter with 3-char (moderate filtering)
t.restart();
filterEdit->setText(QStringLiteral("STR"));
qint64 tFilter3 = t.nsecsElapsed();
// Measure filter with 6-char (narrow results)
t.restart();
filterEdit->setText(QStringLiteral("STRUCT"));
qint64 tFilter6 = t.nsecsElapsed();
// Measure clear filter (back to grouped view)
t.restart();
filterEdit->setText(QString());
qint64 tClear = t.nsecsElapsed();
popup->hide();
QApplication::processEvents();
qDebug() << "";
qDebug().noquote() << "=== Large SDK Benchmark (5000 structs) ===";
qDebug().noquote() << QString(" setTypes: %1 ms").arg(ms(tSetTypes));
qDebug().noquote() << QString(" popup() call: %1 ms").arg(ms(tPopupCall));
qDebug().noquote() << QString(" processEvents: %1 ms").arg(ms(tProcessEvents));
qDebug().noquote() << QString(" popup total: %1 ms").arg(ms(tShow));
qDebug().noquote() << QString(" popup2() call: %1 ms (warm)").arg(ms(tPopup2));
qDebug().noquote() << QString(" processEvents2: %1 ms (warm)").arg(ms(tProcess2));
qDebug().noquote() << QString(" popup2 total: %1 ms (warm)").arg(ms(tPopup2 + tProcess2));
qDebug().noquote() << QString(" filter 'S': %1 ms").arg(ms(tFilter1));
qDebug().noquote() << QString(" filter 'STR': %1 ms").arg(ms(tFilter3));
qDebug().noquote() << QString(" filter 'STRUCT': %1 ms").arg(ms(tFilter6));
qDebug().noquote() << QString(" clear filter: %1 ms").arg(ms(tClear));
QVERIFY(tSetTypes > 0);
delete popup;
}
// ── Popup data model ──
void testPopupListsRootStructs() {