Engine & platform

Verbesserung der Burst Inspector Suchleistung

JONAS REHOLT / UNITY TECHNOLOGIESStudent Software Developer
Jul 11, 2023|7 Min.
Verbesserung der Burst Inspector Suchleistung
Diese Website wurde aus praktischen Gründen für Sie maschinell übersetzt. Die Richtigkeit und Zuverlässigkeit des übersetzten Inhalts kann von uns nicht gewährleistet werden. Sollten Sie Zweifel an der Richtigkeit des übersetzten Inhalts haben, schauen Sie sich bitte die offizielle englische Version der Website an.

Mein Name ist Jonas Reholt und ich bin Student und arbeite im Burst-Team. In meinem Blog möchte ich Ihnen von meiner Optimierungsreise berichten, die die jüngsten Leistungsänderungen am Burst Inspector möglich gemacht hat. Die Burst Inspector-Suche ist jetzt 13 Mal schneller, so dass sich Entwickler bei der Optimierung von Projekten schneller auf den Code konzentrieren können, der ihnen wichtig ist.

Lesen Sie weiter, um zu erfahren, wie Sie den Unity Profiler verwenden können, um Leistungsengpässe in Ihrem Programm zu untersuchen und zu beheben.

Einführung in den Burst Inspector

Der Unity Burst Compiler wandelt Ihren C#-Code in hochoptimierten Assembler-Code um. Mit dem Burst Inspector können Sie diesen Assembler-Code direkt im Unity-Editor inspizieren, so dass Sie für die einfache Code-Inspektion keine externen Tools verwenden müssen.

Wenn Sie den Burst Inspector zum ersten Mal öffnen und einen Zielauftrag zur Anzeige auswählen, sehen Sie ein Fenster ähnlich der folgenden Abbildung.

Das Fenster Burst Inspector zeigt den kompilierten Assembler-Code eines Burst-Zielauftrags
Das Fenster Burst Inspector zeigt den kompilierten Assembler-Code eines Burst-Zielauftrags

Wie Sie sehen können, bietet der Burst Inspector Syntaxhervorhebung, Verzweigungspfeile und vieles mehr.

Der Inspektor wird versuchen, zu der Baugruppe zu scrollen, die die gewählte Zielfunktion implementiert, aber es ist auch nützlich, die Baugruppenansicht nach bestimmten Anweisungen, Kommentaren usw. zu durchsuchen. Das bringt uns zum Thema dieses Blogbeitrags.

Verbesserung der Leistung der Textsuche

Um die Suche durchzuführen, muss der Inspektor die ursprüngliche Baugruppenausgabe durchsuchen und diese Indizes in Positionen in der Inspektoransicht umwandeln. Die ursprüngliche Suchfunktion folgte dem unten gezeigten Muster und stützte sich stark auf die Implementierung von System.String.IndexOf(*).

while (assemblyCode.IndexOf(key, accIdx) >= 0) {
// ...	
// Do logic for handling search hits
// ...
}

Die Ausführung der obigen Suche auf 135.582 Zeilen Assemblercode für einen gemeinsamen Suchtreffer (21.769 Treffer insgesamt) ergab eine Ausführungszeit von etwa 12 Sekunden für die erste Suche und etwa 5 Sekunden für nachfolgende Suchen. Das ist nicht wirklich eine wünschenswerte Wartezeit für ein GUI-Ereignis, also mussten wir etwas tun. Wenn Sie die Suche durch den Unity Profiler laufen lassen, zeigt sich, dass 37,3 % der Ausführungszeit auf IndexOf(*) entfallen, wie Sie unten sehen.

Profilierter Durchlauf der Suche nach einer gemeinsamen Zeichenfolge im Burst Inspector
Profilierter Durchlauf der Suche nach einer gemeinsamen Zeichenfolge im Burst Inspector

Eine sinnvolle Optimierung muss die Abhängigkeit von dieser Funktion beseitigen, entweder durch eine angepasste Implementierung oder durch eine Änderung des Algorithmus insgesamt. Unabhängig davon, welcher Algorithmus verwendet wird, muss die gesamte Zeichenkette durchlaufen werden. Es ist also eine eigene Implementierung für die Suche nach Übereinstimmungen erforderlich. Daher schien es angebracht, die Optimierung damit zu beginnen, den ursprünglichen Algorithmus beizubehalten, aber eine eigene IndexOf-Funktion zu erstellen.

Die 3,34 Sekunden, die für LongTextArea.GetFragNrFromBlockIdx() benötigt werden, sind auf das Abrufen von ungefärbtem Assemblercode zurückzuführen. Dies wird zur Durchführung der Suche verwendet. Der Burst Inspector speichert den Assembler-Code derzeit zweimal - einmal formatiert für das Rendering und einmal unformatiert.

Das Schreiben einer benutzerdefinierten Funktion hat außerdem den angenehmen Nebeneffekt, dass die Anzahl der Aufrufe reduziert wird, da es derzeit für jeden Suchtreffer einen Aufruf gibt, plus einen.

Der Quellcode von IndexOf(*) offenbart viele Sicherheitsüberprüfungen, die für eine robuste allgemeine Implementierung erforderlich sind. In unserem Fall können wir jedoch sicher davon ausgehen, dass die meisten dieser Prüfungen zutreffen. Wenn Sie versuchen wollen, jeden Tropfen Leistung herauszuholen, sollten Sie eine C-ähnliche Funktion erstellen, um Dinge wie Bounds Check zu vermeiden.

Sie können die Funktion nach dem folgenden Pseudocode schreiben, wobei IsKeyMatch(*) einfach prüft, ob der Schlüssel eine Übereinstimmung ist oder nicht.

List<int> Search(string assemblyCode, string key, int accIdx) {
     var hits = new List<int>();
     for (i = accIdx; i < assemblyCode.len - key.len; i++) {
	     if (IsKeyMatch(assemblyCode, key, i)) {
		     hits.add(i);
	     	     i += key.len-1;
          }
     }
     return hits;
}

Da C# jedoch eine verwaltete Sprache ist, erfordert diese C-ähnliche Funktion, dass Sie die verwendeten verwalteten Objekte anheften, damit der Garbage Collector die Speicheradresse nicht verschiebt. Hier ist der Standardcode:

unsafe {
	fixed (char* source = assemblyCode) {
		fixed (char* needle = key) {
			CustomIndexOf(source, key)
		}
	}
}

Durch diese Kombination können Sie die ursprüngliche while-Schleife in einen einzigen Aufruf des Indexfinders und die Logik für die Verarbeitung der Suchtreffer aufteilen:

matches = FindAllMathces(text, key)
foreach match {
	...
	Do logic for handling search hits
	...
}

Was waren die Gewinne? Anhand des kleinen Beispiels von vorhin ergibt diese Änderung des Codes eine 6,6-fache Beschleunigung beim ersten Aufruf und eine 13,2-fache Beschleunigung bei nachfolgenden Aufrufen (gemessen als alt/neu). Der geringere Geschwindigkeitszuwachs bei der anfänglichen Suche ist auf den Overhead zurückzuführen, der durch das Laden der unformatierten Baugruppe entsteht, um zu vermeiden, dass Übereinstimmungen in Farbzeichenfolgen gefunden werden.

Laufzeitmessungen der Textsuche im Burst Inspector
Laufzeitmessungen der Textsuche im Burst Inspector

Mit diesen Verbesserungen benötigen Suchvorgänge mit hoher Last und etwas weniger als 22.000 Treffern jetzt etwa 1,8 Sekunden für die erste Suche und etwa 0,4 Sekunden für nachfolgende Suchen. Dadurch ist der Burst Inspector für große Baugruppen besser geeignet, da die Zeit nicht mehr ausreicht, um bei jeder Suche eine Tasse Tee zu kochen.

Mit dem Paket Burst 1.8.7 können Sie jetzt von dieser Leistungsverbesserung profitieren.

Suchen Sie mehr über Burst? Verbinden Sie sich mit uns im Burst Forum. Achten Sie auf weitere neue technische Blogs von anderen Unity-Entwicklern im Rahmen der fortlaufendenSerieTech from the Trenches.