Jump to content
Unity Insider Forum
Psychros

Vector3.Distance anscheinen nicht richtig optimiert

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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...

Share this post


Link to post
Share on other sites

Das wäre gut möglich. Dann sind die anderen Methoden vermutlich auch ein wenig langsamer als selbstgeschriebene. Müsste man natürlich austesten.

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

Jau, das Repo meinte ich.

 

Tatsächlich braucht die nachimplementierte Version genauso viel Zeit wie Vector3.Distance.

Die Version ist echt ein wenig Käse...

Share this post


Link to post
Share on other sites

Hi,

das ist interessant.

Mich würde mal interessieren ob bei "double" gleiche Messwerte entstehen wie bei "float".

 

Kannst Du das mal durchlaufen lassen?

 

 

MfG Felix

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

Das ist von C# eine Klasse die nennt sich Stopwatch und dient dazu Zeit zu messen.

Start sagt einfach starte die Zeitmessung, Stop stoppe die Zeitmessung und ElapsedMilliseconds gibt die vergangene Zeit in Millisekunden zurück.

  • Like 1

Share this post


Link to post
Share on other sites

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, );

  • Like 2

Share this post


Link to post
Share on other sites

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.

  • Like 2

Share this post


Link to post
Share on other sites

Ich habe das Ganze eben noch als Build ausprobiert. Der Build ist bei mir ca. 10ms schneller als der Editor. Der Unterschied zwischen der Unity und der Custom Methode bleibt gleich.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
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.

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×