Jump to content
Unity Insider Forum

Vector3.Distance anscheinen nicht richtig optimiert


Psychros

Recommended Posts

Hallo Leute,

ich habe jetzt spaßeshalber mal eine eigene Vector3.Distance-Methode gegen das Original antreten lassen. Da beider Methoden nur extrem wenig Zeit benötigen, werden beide 1.000.000 Male ausgeführt.

Hier ist der Experimentaufbau:

Vector3 v = new Vector3(1, 5, -3);
	//Vector3.Distance
	watch.Start();
	for (int i = 0; i < 1000000; i++)
	{
		Vector3.Distance(Vector3.zero, v);
	}
	watch.Stop();
	//distance
	watch2.Start();
	for (int i = 0; i < 1000000; i++)
	{
		distance(Vector3.zero, v);
	}
	watch2.Stop();

	print("Vector3.Distance: " + watch.ElapsedMilliseconds);
	print("distance: " + watch2.ElapsedMilliseconds);

 

Hier ist die von mir geschriebene Methode:

public static float distance(Vector3 a, Vector3 
{
	float x = b.x - a.x;
	float y = b.y - a.y;
	float z = b.z - a.z;
	return Mathf.Sqrt(x*x + y*y + z*z);
}

 

Um Zufallsergebnisse auszuschließen, wurde der Code 10 mal ausgeführt.

Dabei kamen folgende Ergebnisse zustande. Links steht jeweils das Ergebnis von Vector3.Distance und rechts das Ergebnis meiner Methode.

63 54

62 60

62 55

63 55

63 54

64 54

62 54

63 57

63 54

62 54

 

Wie man sehen kann, ist dabei die eigene Methode immer um ein par Millisekunden schneller als Vector3.Distance. Im Durchschnitt braucht Vector3.Distance 62,7ms und meine eigene Methode 55,1ms.

Mich würde interessieren, warum Vector3.Distance durchschnittlich etwa 13% langsamer als die andere Methode ist. Im Prinzip machen beide Methoden das gleiche.

Vielleicht hat ja einer von euch eine Idee, woran das liegen könnte. Es wäre auch interessant zu wissen, ob ihr überhaupt Vector3.Distance oder selber geschriebene Methoden nutzt.

 

MfG Tobias

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du solltest nicht zehn Mal beide Methoden ausführen, sondern ein paar hundert Mal die erste und danach ein paar hundert Mal die zweite, dann lass das Durchschnittsergebnis berechnen.

 

In deinem Fall kann ich mir sehr gut vorstellen, dass Zwischenergebnisse gecached werden und die als zweites aufgerufene Methode daher schneller ist.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe das ganze jetzt nochmal durchlaufen, allerdings wurde die Schleife jetzt 1.000.000.000 Mal durchlaufen.

Das ganze wurde mehrfach mit unterschiedlichen Situatuationen durchlaufen, z.B. wurde die Reihenfolge der Methoden geändert oder es wurde nur eine Methode getestet.

Das Ergebnis ist auf jeden Fall immer dasselbe. Vector3.Distance braucht immer durchschnittlich 62-64ms(heruntergerechnet auf 1.000.000 Aufrufe) und meine Methode braucht immer zwischen 53 und 55 ms.

Vielleicht werden ja in Vector3.Distance teile der Rechnung zwischengespeichert, welche in meiner Methode zusammengefasst wurden. Diese Anweisung könnte man z.B. aufteilen:

x*x + y*y + z*z

Auf jeden Fall sollten 1.000.000.000 Durchläufe reichen, um ein aussagekräftiges Ergebnis zu bekommen.

Ich werde jetzt noch testen, ob

Aber auch Vector3.Distance sollte im Normalfall ausreichend schnell sein.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich hatte deinen Code völlig falsch gelesen, du machst das ja schon richtig... -.-

Ich kriege übrigens ähnliche Ergebnisse.

 

Irgendwo hatte mal jemand mit Erlaubnis von Unity Tech Teile der Engine dekompiliert und irgendwo hochgeladen (GitHub?).

Ich finde das Repo nur leider gerade nicht mehr.

 

Meine neue Vermutung ist, dass der Aufruf von Vector3.Distance in eine DLL hinein geht und dadurch vielleicht langsamer wird...

Alternativ bedeutet das, dass da Methodenaufrufe nicht entpackt wurden, was danach klingt, dass die UnityEngine.dll mit einem ziemlich unfähigen (bzw. schlecht eingestellten) Compiler erstellt wird...

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hmm ich weiß nicht ob du das hier meinst: https://github.com/MattRix/UnityDecompiled/blob/master/UnityEngine/UnityEngine/Vector3.cs

 

Aber dort wurde die Methode so implementiert

public static float Distance(Vector3 a, Vector3 
 {
  Vector3 vector = new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
  return Mathf.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
}

 

Einziger unterschied, aus irgendeinem Grund legen die extra eine Instanz von Vector3 dafür an. Das könnte theoretisch mehr Leistung fressen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Sorry, wenn ich quer reinfrage... "watch.Start(); watch.Stop(); watch.ElapsedMilliseconds"

Was ist "watch" und wie nutze ich das? Finde dazu nichts in der Referenz, oder ich bin zu blöd zum suchen. Oder muss man da nur etwas includen, dass das funktioniert?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn ihr selber mal was messen wollt, ich hab das vorhin mal hübsch gemacht:

   static void Test(string label, Action a, int count = 1000000)
   {
    UnityEngine.Debug.Log("Testing " + label + ": " + Measure(a, count) + "ms average.");
   }

   static double Measure(Action a, int count = 1000000)
   {
    var watch = new Stopwatch();
    watch.Start();
    for(var i = 0; i < count; ++i)
    {
	    a.Invoke();
    }
    watch.Stop();
    return watch.ElapsedMilliseconds * 1d / count;
   }

 

Nicht vergessen, System und System.Diagnostics zu importieren.

Angewendet werden kann das dann so:

Test("MyDistance", () => MyDistance(a, );
Test("Vector3.Distance", () => Vector3.Distance(a, );

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe die Methode jetzt nochmal ein wenig umstrukturiert:

public static double distance(Vector3 a, Vector3 
{
	float x = b.x - a.x;
	float y = b.y - a.y;
	float z = b.z - a.z;

	return System.Math.Sqrt(x * x + y * y + z * z);
}

 

Anstelle der Klasse Mathf benutze ich nun System.Math für die Wurzel.

Dies habe ich gemacht, da Mathf von Unity stammt und demnach auch in der potentiellen dll liegt.

Auch liefert die Methode jetzt anstelle des float einen double.

Für 1.000.000 Durchläufe braucht sie jetzt anstatt 53-55ms nur noch ca. 43-47ms.

Anscheinend liegt dieses Performanceproblem bei allen Unitymethoden vor.

 

#Benni :

Mit doubles anstelle der floats gibt es keine großen Performanceunterschiede.

Hier kommt es dann hauptsächlich darauf an, ob man Mathf oder Mathias benutzt.

Da Mathf nur floats akzeptiert, müsste man die doubles erst in floats konvertieren.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe jetzt nochmal ein wenig herumexperimentiert und die Methode Vector3.Distance einfach mal kopiert und in einem eigenen Script benutzt. Das diese etwas langsamer als die Custom-Methode ist liegt tatsächlich daran, dass ein unnötiges Vector3-Objekt erstellt wird.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 1 year later...

Ich habe nach langer Zeit gerade noch einmal an die Optimierung der Funktion gemacht und auch die alten Funktionen ein weiteres mal gemessen, da sich in der Zwischenzeit mein System verändert hat.

public static double Distance(Vector3 a, Vector3 b)
{
	return System.Math.Sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y) + (b.z - a.z) * (b.z - a.z));
}

Die Funktion unterscheidet sich von der alten dadurch, dass keine neuen Variablen initialisiert werden und somit weniger Speicheraufrufe erfolgen.

Ausprobiert habe ich auch noch, die Vektoren per ref zu übergeben, mit dem Gedanken, dass sie dann nicht kopiert werden müssen. Dies hat allerdings keinen messbaren Vorteil gebracht. Optimierungspotential besteht immer noch, indem man die Wurzelberechnung aus der Math.Sqrt Methode direkt in die Funktion Distance verlagert. Immerhin kostet jeder Funktionsaufruf Zeit.

Die Zeiten wurden erneut für 1.000.000 Durchläufe mit Stopwatch berechnet.

Vector3.Distance: 51-54ms

Die alte eigene Methode aus einem früheren Beitrag oben: 28-30ms

Die weiter optimierte Methode: 23-24ms

 

Mich wundert immer noch, dass auch in den neuen Unityversionen dieser Flaschenhals nicht behoben wurde. Distanzen werden ja doch häufiger benutzt und der Zeitunterschied von 23 zu 51 ms ist beträchtlich.

 

Wenn man übrigens die Distanz direkt in der aufrufenden Methode ausführt und keinen Methodenaufruf benutzt, braucht die Berechnung nur ca 13ms.

Der Testcode sieht dann so aus:

Vector3 a = Vector3.up;
Vector3 b = Vector3.right;
for (int i = 0; i < 10000000; i++)
{
      System.Math.Sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y) + (b.z - a.z) * (b.z - a.z));
}
Chunk.watch.Stop();
print(Chunk.watch.ElapsedMilliseconds / 10);

Eine ähniche Optimierung erzielt man vermutlich auch durch weglassen von Math.Sqrt. Ich schätze die Zeit dann auf 3-7ms ein. Wenn man weiterhin eine Methode Distance aufrufen möchte, sind es vermutlich wieder 13-15ms.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 19 Minuten schrieb Psychros:

Optimierungspotential besteht immer noch, indem man die Wurzelberechnung aus der Math.Sqrt Methode direkt in die Funktion Distance verlagert. Immerhin kostet jeder Funktionsaufruf Zeit.

der Zeitunterschied von 23 zu 51 ms ist beträchtlich.

Das ist zwar richtig, allerdings muss man auch sehen mit welchem Aufwand man sich die Optimierung leistet. 
Natürlich könnte ich jetzt herkommen und alle built-in Funktionen bis auf Anschlag optimiert reimplementieren. Allerdings ging eure Diskussion damals schon über 2 Tage hinweg und dabei handelt es sich um 1mio Aufrufe mit einem Performancegewinn von ~55%
Wie oft hat man Vector3.Distance schon in einem Update Zyklus? Mich würde es wundern wenn man überhaupt 100x im gesamten Programm Vector3.Distance benutzen würde und das wäre dann (runtergerechnet von den Testergebnissen) ein Performancegewinn von 5,1 Nanosekunden auf 2,3 Nanosekunden. Das ist echt nicht der Rede wert.
Wenn ich meinen Profiler anwerfe, mich mal 2 Stunden hinsetze und eine der teureren Methoden auch nur 5% schneller machen kann würde das wahrscheinlich mehrere Millisekunden rausholen. 

Was ich damit sagen will: Häng dich nicht mit sowas auf, sondern optimiere lieber das was wirklich Zeit frisst. Solange du dein FPS Ziel erreichst ist doch alles gut und falls nicht gibt es wahrscheinlich 1000 Methoden, bei denen es sich eher lohnt mal ein paar Stunden zu investieren und zu optimieren.
Es sei denn natürlich du musst Vector3.Distance tatsächlich mehrere zehntausend Mal pro Update benutzen - warum auch immer. Aber dann ist im Zweifelsfall auch irgendwas am grundlegenden Design des Programms komisch/optimierbar, statt an der Methode selbst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...