XmlElementCollection::child(): linear sibling walk instead of item(i)

The child lookup iterated parent_element.childNodes() via item(i), and
QDomNodeList::item() walks the sibling chain from the start on every
call - making the loop quadratic in the number of children, with an
extra QList allocation and a second pass on top. This lookup runs
several times per element instance while loading a project, against the
"import" category that holds every embedded definition, so the cost
scales with (instances x embedded definitions).

Replace it with a firstChildElement()/nextSiblingElement() walk with an
early return. Same semantics (first tag+name match in document order).

Measured on the Kaefer_1303 reference project (3.9 MB, 23 folios,
432 instances, media of 6 runs, Windows/MinGW, same GCC for both):

           before      after
  Qt5      5.116 s     5.068 s
  Qt6      6.683 s     4.640 s   (-31 %)

This removes the entire Qt6 load-time regression discussed in #553 -
Qt6 goes from +31 % slower to 8 % faster than Qt5 on the very project
that exposed it (Qt6''s QDom makes the quadratic pattern much more
expensive than Qt5''s did). Smaller projects gain too (3.4 MB example:
-9 % on Qt6).

(cherry picked from commit 0d4ef8eca27601c37ba2b75d7c058e9d8e2beea4)
This commit is contained in:
Dieter Mayer
2026-07-28 14:07:51 +02:00
parent 66129fd15c
commit de82b2738f
@@ -186,23 +186,22 @@ QDomElement XmlElementCollection::child(const QDomElement &parent_element,
if (parent_element.ownerDocument() != m_dom_document)
return QDomElement();
//Get all childs element of parent_element
QDomNodeList child_list = parent_element.childNodes();
QString tag_name(child_name.endsWith(".elmt")? "element" : "category");
QList <QDomElement> found_dom_element;
for (int i=0 ; i<child_list.length() ; i++)
/* Walk the siblings directly instead of the previous
* childNodes()+item(i) double pass: QDomNodeList::item(i) restarts
* from the first child every call, which made this loop quadratic
* in the number of children. This lookup runs several times per
* element instance while loading a project, on the "import"
* category that holds every embedded definition. */
const QString tag_name = child_name.endsWith(QLatin1String(".elmt"))
? QStringLiteral("element") : QStringLiteral("category");
for (QDomElement child_element = parent_element.firstChildElement(tag_name) ;
!child_element.isNull() ;
child_element = child_element.nextSiblingElement(tag_name))
{
QDomElement child_element = child_list.item(i).toElement();
if (child_element.tagName() == tag_name)
found_dom_element << child_element;
if (child_element.attribute(QStringLiteral("name")) == child_name)
return child_element;
}
if (found_dom_element.isEmpty()) return QDomElement();
foreach (QDomElement elmt, found_dom_element)
if (elmt.attribute("name") == child_name)
return elmt;
return QDomElement();
}