Verbesserung der CPU-Auslastung in Qt 3D

Seit der Veröffentlichung von Qt 5.6, unserer vorherigen LTS-Version (Long Term Support), wurden viele Verbesserungen an Qt 3D vorgenommen. Ingenieure von KDAB und The Qt Company haben hart daran gearbeitet, neue Funktionen in Qt 5.9 LTS zu integrieren, von denen viele in What's New in Qt 3D with Qt 5.9 (http://blog.qt.io/blog/2017/ 05/24/qt3d/) in einem Blogbeitrag von Sean Harmer von KDAB. Obwohl sich viele Funktionen noch in der Entwicklung befinden (z. B. Vulkan-Backend), hat sich der Fokus in den letzten Versionen auf Leistung und Stabilität verlagert. Die Effizienz hat sich seit Qt 5.6 erheblich verbessert, insbesondere bei komplexen Szenen und Szenen mit vielen Grafiken.

Szenen mit vielen Ansichtsfenstern führen normalerweise zu einer großen Anzahl von Rahmendiagrammen, weil jedes Ansichtsfenster einem Blattknoten entspricht. Wenn Sie mit dem Konzept eines Rahmendiagramms in Qt 3D und seiner Leistungsfähigkeit nicht vertraut sind, sollten Sie Beitrag aus dem kdab.com-Blog von Paul Lemarie . Unten ist ein Screenshot von einem unserer internen Tests; eine ziemlich einfache (und farbenfrohe) Szene mit 28 Ansichtsfenstern:


Die CPU-Auslastung in diesem Test ist in Qt 5.9.2 im Vergleich zu Qt 5.6.2 erheblich reduziert, und Qt arbeitet mit den KDAB-Ingenieuren an einer Reihe von Änderungen, von denen wir erwarten, dass sie die CPU-Auslastung in Qt 5.11 noch weiter reduzieren werden:

Viele der Leistungsverbesserungen wurden auf die Qt 3D Studio-Portierung portiert, die auf Qt 3D basiert. Obwohl die Veröffentlichung der Laufzeitumgebung für nächstes Jahr geplant ist, fügen wir der aktuellen Qt 5.9.x LTS-Serie bereits Leistungsverbesserungen hinzu. Hier sind einige Testergebnisse aus unseren internen Qt3D-Studio-Beispielen:

Leistungsverbesserungen wurden in vielen Teilen von Qt 3D hinzugefügt. Beispielsweise haben wir Unterstützung für effiziente Dateiformate wie glTF2 hinzugefügt. In diesem Beitrag werfen wir einen genaueren Blick auf einige der Änderungen, die wir vornehmen, um die CPU-Auslastung zu reduzieren, und in einem späteren Beitrag besprechen wir die Reduzierung des Speicherverbrauchs.

Verbesserter Job-Abhängigkeits-Solver

Eine Leistungsverbesserung, die wir vorgenommen haben, ist der Qt 3D-Auftragsabhängigkeitslöser. Qt 3D teilt die Arbeit, die für jeden Frame erledigt werden muss, in separate, kleinere Jobs auf, die parallel erledigt werden können. Jobs sind Teil der flexiblen Backend/Frontend-Architektur von Qt 3D, die die Schnittstelle im Haupt-Thread vom Backend trennt, das aus Aspekten besteht, die Rendering, Eingabe und Animation behandeln (mehr dazu in der Qt 3D-Übersichtsdokumentation ).

Das Back-End führt Jobs aus verschiedenen Aspekten des Thread-Pools aus, und jeder Job kann Abhängigkeiten von anderen Jobs definieren, die vor ihm ausgeführt werden sollen. Diese Abhängigkeiten müssen effizient aufgelöst werden, da Jobs häufig von einem Frame zum nächsten wechseln. Während dies bei geringer Anzahl von Jobs einfach ist, wird es bei komplexen Szenen mit großen Frames immer arbeitsintensiver.

Beim Profilieren unserer Beispiele mit Callgrind haben wir Leistungsengpässe in bestimmten Teilen des Jobabhängigkeitsauflösers festgestellt. Insbesondere ändert sich der große QVector aller Abhängigkeiten jedes Mal, wenn der Job abgeschlossen ist, und die entsprechenden Abhängigkeiten können aus der Liste entfernt werden. Dies reduzierte die Leistung drastisch.

Wir haben begonnen, an einer Lösung zu arbeiten, bei der wir den QVector vollständig loswerden und zwei Listen speichern, die einem Job zugeordnet sind: Eine Liste enthält, wovon der Job abhängt, und die andere Liste, wovon der Job abhängt.

class AspectTaskRunnable {
    // ... other definitions
    QVector m_dependencies;
    QVector m_dependers;
};

Mit dieser Lösung kann der Job, wenn er abgeschlossen ist, seine m_dependers-Liste durchgehen und sich selbst aus der m_dependencies-Liste in jedem der m_dependers entfernen. Wenn die Liste m_dependers leer ist, kann dieser Job gestartet werden. Jetzt haben wir jedoch viele kleine QVectors, die sich ständig ändern. Dies ist zwar besser als die Größenänderung eines großen QVectors, aber immer noch nicht optimal.

Schließlich haben wir festgestellt, dass es nicht notwendig ist, nachzuverfolgen, wovon der Job abhängt und wovon der Job abhängt, da sich Abhängigkeiten nicht ändern können, während ein Job ausgeführt wird. Für jede Aufgabe reicht es aus zu wissen, welche Aufgaben von ihr abhängen und von wie vielen Aufgaben sie abhängt.

class AspectTaskRunnable {
    // ... other definitions
    int m_dependencyCount = 0;
    QVector<AspectTaskRunnable*> m_dependers;
};

Immer wenn eine Aufgabe abgeschlossen ist, gehen wir die Liste der davon abhängigen Aufgaben durch und subtrahieren die Anzahl der Abhängigkeiten in ihnen um eins. Der endgültige Code sieht ungefähr so aus (schamlos vereinfacht für die Lesbarkeit):

void QThreadPooler::taskFinished(AspectTaskRunnable *job)
{
    const auto &dependers = job->m_dependers;
    for (auto &depender : dependers) {
        depender->m_dependencyCount--;
        if (depender->m_dependencyCount == 0) {
            m_threadPool.start(depender);
        }
    }
}

Durch die Implementierung dieser Änderung trug der Job-Abhängigkeits-Solver nur noch geringfügig zur CPU-Auslastung bei, und wir konnten uns auf andere Engpässe konzentrieren.

QThreadPool-Leistung verbessern

Auch andere Teile von Qt nutzen die in unseren Tests gefundenen Optimierungen. Beispielsweise verwendet Qt 3D QThreadPool von Qt Core, um Jobs automatisch zu verwalten und sie verschiedenen Threads zuzuweisen. Wie im vorherigen Fall wurde jedoch QThreadPool verwendet, um Jobs in einem QVector zu speichern, der seine Größe jedes Mal änderte, wenn der Job endete. Bei wenigen Jobs ist das kein großes Problem, bei komplexen Qt-3D-Szenen mit vielen Jobs ist es aber plötzlich zum Flaschenhals geworden.

Wir haben uns entschieden, die QThreadPool-Implementierung zu ändern, um größere "Warteschlangenseiten" zu verwenden und Zeiger auf diese Seiten in einem QVector zu platzieren. Auf jeder Seite verfolgen wir den Index des ersten Jobs in der Warteschlange und den Index des letzten Jobs in der Warteschlange:

class QueuePage {
    enum {
        MaxPageSize = 256;
    }; 

    // ... helper functions, etc.

    int m_firstIndex = 0;
    int m_lastIndex = -1;
    QRunnable *m_entries[MaxPageSize];
};

Jetzt müssen wir nur noch den ersten Index erhöhen, wenn der Job abgeschlossen ist, und den letzten Index erhöhen, wenn der Job hinzugefügt wird. Wenn auf der Seite kein Platz mehr ist, weisen wir eine neue zu. Es ist eine einfache Implementierung auf niedriger Ebene, aber effizient.

Zwischenspeichern der Ergebnisse bestimmter Jobs

Wir haben dann festgestellt, dass bestimmte Jobs sehr CPU-intensiv sind. Einige dieser Jobs, wie z. B. der QMaterialParameterGathererJob, haben in jedem Frame viel Arbeit geleistet, selbst wenn die Ergebnisse der vorherigen Frames dieselben waren. Es war eine klare Gelegenheit, Ergebnisse zwischenzuspeichern, um die Leistung zu verbessern. Sehen wir uns zunächst an, was QMaterialParameterGathererJob tut.

In Qt 3D können Sie die Werte jedes in QRenderPass definierten Parameters überschreiben, indem Sie ihn auf die QTechnique, QEffect oder QMaterial setzen, die diesen Renderdurchgang verwenden. Jeder Parameter wiederum wird verwendet, um einen einheitlichen Wert im endgültigen Shader-Programm zu definieren. Dieser Code zeigt ein QML-Beispiel, bei dem die Option "Farbe" auf allen Ebenen festgelegt ist:

Material {
    parameters: [
        Parameter { name: "color"; value: "red"}
    ]
    effect: Effect {
        parameters: [
            Parameter { name: "color"; value: "blue"}
        ]
        techniques: Technique {
              // ... graphics API filter, filter keys, etc.

              parameters: [
                  Parameter { name: "color"; value: "green"}
              ]
              renderPasses: RenderPass {
                  parameters: [
                      Parameter { name: "color"; value: "purple"}
                  ]
                  shaderProgram: ShaderProgram {
                      // vertex shader code, etc.

                      fragmentShaderCode: "
                          #version 130
                          uniform vec4 color;
                          out vec4 fragColor;
                          void main() {
                              fragColor = color;
                          }
                      "
                  }
              }
          }
    }
}

Um den endgültigen Wert eines in einem Shader-Programm verwendeten Parameters herauszufinden, durchsucht QMaterialParameterGathererJob alle Materialien in der Szene und findet die geeigneten Effekte, Methoden und Renderdurchläufe. Dann bestimmen wir durch Priorisierung der in QMaterial, QEffect, QTechnique und QRenderPass angegebenen Parameter den endgültigen Wert des Parameters.In diesem Fall ist der Wert rot, weil die QMaterial-Parameter die höchste Priorität haben.

Das Sammeln aller Parameter ist in großen Szenen mit vielen Materialien ziemlich mühsam und hat sich bei einigen unserer Qt 3D Studio-Beispiele als Engpass erwiesen. Also entschieden wir uns, die von QMaterialParameterGathererJob gefundenen Parameterwerte zwischenzuspeichern, stellten aber schnell fest, dass der Cache immer ungültig würde, wenn sich die Werte in jedem Frame ändern würden. Dies ist ein häufiger Fall, insbesondere wenn die Parameter animiert sind. Stattdessen haben wir uns dafür entschieden, Zeiger auf QParameter-Objekte anstelle ihrer Werte zu cachen. Die Werte werden dann außerhalb des Caches gespeichert und nur bei Bedarf abgerufen. Das Zwischenspeichern von Ergebnissen führte zu einer enormen Leistungssteigerung in Szenen mit mehreren Parametern, da wir diese Arbeit nur bei großen Änderungen an der Szene durchführen mussten, z. B. beim Hinzufügen von Materialien.

Wir haben mit vielen ähnlichen Fällen gearbeitet, in denen wir einige unserer größeren Beispiele genommen, sie profiliert, Engpässe in bestimmten Jobs identifiziert und daran gearbeitet haben, Möglichkeiten zu finden, die Leistung zu verbessern oder Ergebnisse zwischenzuspeichern. Glücklicherweise macht es das jobbasierte System in Qt 3D einfach, bestimmte Jobs unabhängig voneinander zu optimieren oder zwischenzuspeichern, sodass Sie weitere Verbesserungen in den kommenden Versionen von Qt 3D erwarten können.

Gepostet von: Svenn-Arne Dragly | Donnerstag, 16.11.2017

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken