StructPerformance: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
Zeile 5: | Zeile 5: | ||
== Call by Value == | == Call by Value == | ||
Bei C# geschehen Parameterübergaben bei Funktionsaufrufen (genauso wie bei Java) implizit immer in Form von " | Bei C# geschehen Parameterübergaben bei Funktionsaufrufen (genauso wie bei Java) implizit immer in Form von "call by value". Das heisst dass übergebene Variablen für die Verwendung in Methoden kopiert werden. | ||
Hier ist ein Codebeispiel dazu: | Hier ist ein Codebeispiel dazu: | ||
Zeile 28: | Zeile 28: | ||
System.Console.WriteLine(test.number); // gibt 7 aus | System.Console.WriteLine(test.number); // gibt 7 aus | ||
ChangeNumber2(test); | ChangeNumber2(test); | ||
System.Console.WriteLine(test.number); // gibt | System.Console.WriteLine(test.number); // gibt 4 aus | ||
} | } | ||
Zeile 37: | Zeile 37: | ||
public void ChangeNumber2(Test test) | public void ChangeNumber2(Test test) | ||
{ | { | ||
test.number = | test.number = 4; | ||
} | } | ||
} | } | ||
Zeile 46: | Zeile 46: | ||
Die Methode ChangeNumber2 greift dagegen über die kopierte Referenz auf die selbe Klasseninstanz zurück, auf die die ursprüngliche Referenz ebenfalls verweist. Daher wird hier der Zahlenwert geändert und anschließend auch entsprechend ausgegeben. | Die Methode ChangeNumber2 greift dagegen über die kopierte Referenz auf die selbe Klasseninstanz zurück, auf die die ursprüngliche Referenz ebenfalls verweist. Daher wird hier der Zahlenwert geändert und anschließend auch entsprechend ausgegeben. | ||
Das selbe Prinzip gilt ähnlich auch für [[CSharp#Structs|Structs]]. Da Structs allerdings [[CSharp#Datentypen|value types]] sind, wird beim Übergeben einer Struct als Parameter nicht nur eine Referenz, sondern der komplette Inhalt der Struct kopiert! Dies kann je nach Inhalt der Struct (z.B. 16 float Werte bei einer Matrix-Struct) relativ lange dauern und damit die Performance des Spiels massiv verschlechtern wenn Structs sehr häufig als Parameter übergeben werden! | Das selbe Prinzip gilt ähnlich auch für [[CSharp#Structs|Structs]]. Da Structs allerdings [[CSharp#Datentypen|value types]] sind, wird beim Übergeben einer Struct als Parameter nicht nur eine Referenz, sondern der komplette Inhalt der Struct kopiert! Dies kann je nach Inhalt der Struct (z.B. 16 float Werte bei einer Matrix-Struct) relativ lange dauern und damit die Performance des Spiels massiv verschlechtern wenn Structs sehr häufig als Parameter übergeben werden! Das obige Beispiel für Klassen funktioniert mit Structs daher nichtmehr: | ||
<source lang="csharp"> | <source lang="csharp"> | ||
Zeile 66: | Zeile 66: | ||
public void ChangeVector2(Vector3 vector) | public void ChangeVector2(Vector3 vector) | ||
{ | { | ||
vector.x = | vector.x = 4.0; | ||
vector.y = | vector.y = 4.0; | ||
vector.z = | vector.z = 4.0; | ||
} | } | ||
} | } | ||
Zeile 100: | Zeile 100: | ||
== Call by Reference == | == Call by Reference == | ||
== | Implizit gilt bei C# also call by value für Parameterübergaben, es kann allerdings auch explizit ein call by reference erzwungen werden. Dazu gibt es das Schlüsselwort '''ref''', welches vor die Parameter einer Methode geschrieben werden kann. Dies hat dann zur Folge dass keine Kopie, sondern direkt die Referenz auf die entsprechende Klasse oder Struct übergeben wird. Schauen wir uns die call by value Code-Beispiele mit der Verwendung von call by reference an: | ||
<source lang="csharp"> | |||
class Test | |||
{ | |||
public int number; | |||
public Test(int number) | |||
{ | |||
this.number = number; | |||
} | |||
} | |||
public class MyApp | |||
{ | |||
public static void Main() | |||
{ | |||
Test test = new Test(7); | |||
ChangeNumber1(ref test); | |||
System.Console.WriteLine(test.number); // gibt 3 aus | |||
ChangeNumber2(ref test); | |||
System.Console.WriteLine(test.number); // gibt 4 aus | |||
} | |||
public void ChangeNumber1(ref Test test) | |||
{ | |||
test = new Test(3); | |||
} | |||
public void ChangeNumber2(ref Test test) | |||
{ | |||
test.number = 4; | |||
} | |||
} | |||
</source> | |||
<source lang="csharp"> | |||
public class MyApp | |||
{ | |||
public static void Main() | |||
{ | |||
Vector3 test = new Vector3(7.0f); | |||
ChangeVector1(ref test); | |||
System.Console.WriteLine(test); // gibt {X:3 Y:3 Z:3} aus | |||
ChangeVector2(ref test); | |||
System.Console.WriteLine(test); // gibt auch {X:4 Y:4 Z:4} aus | |||
} | |||
public void ChangeVector1(ref Vector3 vector) | |||
{ | |||
vector = new Vector3(3.0); | |||
} | |||
public void ChangeVector2(ref Vector3 vector) | |||
{ | |||
vector.x = 4.0; | |||
vector.y = 4.0; | |||
vector.z = 4.0; | |||
} | |||
} | |||
</source> | |||
Call by reference funktioniert für Structs genauso wie für Klassen. Structs lassen sich somit effizienter übergeben, da keine kompletten Kopien mehr erstellt werden müssen. Allerdings muss man aufpassen dass man beim Ändern einer so übergebenen Struct direkt das ursprüngliche Objekt ändert, was standardmäßig nicht der Fall ist. | |||
Gerade an Codestellen die häufig durchlaufen werden empfielt es sich im Hinblick auf die Performance das call by reference zu nutzen. Die häufig verwendeten Structs wie z.B. die Vector3-Struct verfügen selber bereits über entsprechende statische Methoden für verschiedene Rechenoperationen wie z.B.: | |||
<source lang="csharp"> | |||
void Vector3.Add(ref Vector3 value1, ref Vector3 value2, out Vector3 result); | |||
void Vector3.Multiply(ref Vector3 value1, float scaleFactor, out Vector3 result); | |||
... | |||
</source> | |||
Diese sind wesentlich Effizienter wie ihre pendants mit call by value: | |||
<source lang="csharp"> | |||
Vector3 sum = Vector3.Add(Vector3 value1, Vector2 value2); | |||
Vector3 scaledVec = Vector3.Multiply(Vector3 value1, float scaleFactor); | |||
... | |||
Vector3 sum = vector1 + vector2; | |||
Vector3 scaledVec = vector1 * scaleFactor; | |||
... | |||
</source> | |||
Das '''out''' Schlüsselwort das in den Beispielmethoden ebenfalls verwendet wird dient zum deklarieren von Rückgabeparametern. Diese werden im nächsten Abschnitt erläutert. | |||
== Rückgabeparameter == | |||
== Verwendung von Structs == | |||
http://msdn.microsoft.com/en-us/library/0taef578.aspx | |||
[[Kategorie:Code-Beispiele]] |
Version vom 18. November 2009, 20:36 Uhr
Wenn man in C# mit Structs arbeitet gibt es vor allem im Hinblick auf die Performance ein paar Aspekte die man beachten sollte. Dieser Artikel zeigt die Unterschiede in der Verwendung von Structs und worauf man achten muss.
Call by Value
Bei C# geschehen Parameterübergaben bei Funktionsaufrufen (genauso wie bei Java) implizit immer in Form von "call by value". Das heisst dass übergebene Variablen für die Verwendung in Methoden kopiert werden.
Hier ist ein Codebeispiel dazu:
class Test
{
public int number;
public Test(int number)
{
this.number = number;
}
}
public class MyApp
{
public static void Main()
{
Test test = new Test(7);
ChangeNumber1(test);
System.Console.WriteLine(test.number); // gibt 7 aus
ChangeNumber2(test);
System.Console.WriteLine(test.number); // gibt 4 aus
}
public void ChangeNumber1(Test test)
{
test = new Test(3);
}
public void ChangeNumber2(Test test)
{
test.number = 4;
}
}
Warum ändert die Methode ChangeNumber1 nun bei der Ausgabe die Zahl nicht? Zur kurzen Wiederholung: Dieses Codebeispiel verwendet Klassen und Klassen sind reference types. Beim Aufruf der Methode wird also nicht direkt die Klasse übergeben sondern nur eine Referenz, also ein Hinweis wo sich die dahinterliegende Klasse befindet. Da Parameter implizit mit call by value übergeben werden, also für die Methode kopiert werden, erhält die Methode quasi eine neue Referenz, welche lediglich auf die selbe Klasse verweist wie die ursprüngliche Referenz die beim Methodenaufruf verwendet wurde. Innerhalb der Methode wird die neue Referenz dann auf eine andere Instanz der Klasse Test geändert. Beim Verlassen der Methode wird die Referenz dann automatisch gelöscht, die neue Instanz der Klasse Test mit dem Wert 3 damit also auch da sie nichtmehr referenziert wird.
Die Methode ChangeNumber2 greift dagegen über die kopierte Referenz auf die selbe Klasseninstanz zurück, auf die die ursprüngliche Referenz ebenfalls verweist. Daher wird hier der Zahlenwert geändert und anschließend auch entsprechend ausgegeben.
Das selbe Prinzip gilt ähnlich auch für Structs. Da Structs allerdings value types sind, wird beim Übergeben einer Struct als Parameter nicht nur eine Referenz, sondern der komplette Inhalt der Struct kopiert! Dies kann je nach Inhalt der Struct (z.B. 16 float Werte bei einer Matrix-Struct) relativ lange dauern und damit die Performance des Spiels massiv verschlechtern wenn Structs sehr häufig als Parameter übergeben werden! Das obige Beispiel für Klassen funktioniert mit Structs daher nichtmehr:
public class MyApp
{
public static void Main()
{
Vector3 test = new Vector3(7.0f);
ChangeVector1(test);
System.Console.WriteLine(test); // gibt {X:7 Y:7 Z:7} aus
ChangeVector2(test);
System.Console.WriteLine(test); // gibt auch {X:7 Y:7 Z:7} aus
}
public void ChangeVector1(Vector3 vector)
{
vector = new Vector3(3.0);
}
public void ChangeVector2(Vector3 vector)
{
vector.x = 4.0;
vector.y = 4.0;
vector.z = 4.0;
}
}
Call by value gilt auch für einfache Zuweisungen:
//Klassen
Test class1 = new Test(2);
Test class2 = class1; //class2 ist jetzt eine kopierte Referenz, die auf die selbe Instanz von Test zeigt wie class1
System.Console.WriteLine(class1.number); //gibt 2 aus
System.Console.WriteLine(class2.number); //gibt 2 aus
class2.number = 4;
System.Console.WriteLine(class1.number); //gibt 4 aus
System.Console.WriteLine(class2.number); //gibt 4 aus
class2 = new Test(7); //Die Referenz class2 zeigt jetzt auf eine neue Instanz der Klasse Test
System.Console.WriteLine(class1.number); //gibt 4 aus
System.Console.WriteLine(class2.number); //gibt 7 aus
//Structs
Vector3 struct1 = new Vector3(2.0f);
Vector3 struct2 = struct1; //struct2 ist jetzt eine neue Struct mit den selben Werten wie struct1
System.Console.WriteLine(struct1); //gibt {X:2 Y:2 Z:2} aus
System.Console.WriteLine(struct2); //gibt {X:2 Y:2 Z:2} aus
struct2.x = 4.0f;
System.Console.WriteLine(struct1); //gibt {X:2 Y:2 Z:2} aus
System.Console.WriteLine(struct2); //gibt {X:4 Y:2 Z:2} aus
Call by Reference
Implizit gilt bei C# also call by value für Parameterübergaben, es kann allerdings auch explizit ein call by reference erzwungen werden. Dazu gibt es das Schlüsselwort ref, welches vor die Parameter einer Methode geschrieben werden kann. Dies hat dann zur Folge dass keine Kopie, sondern direkt die Referenz auf die entsprechende Klasse oder Struct übergeben wird. Schauen wir uns die call by value Code-Beispiele mit der Verwendung von call by reference an:
class Test
{
public int number;
public Test(int number)
{
this.number = number;
}
}
public class MyApp
{
public static void Main()
{
Test test = new Test(7);
ChangeNumber1(ref test);
System.Console.WriteLine(test.number); // gibt 3 aus
ChangeNumber2(ref test);
System.Console.WriteLine(test.number); // gibt 4 aus
}
public void ChangeNumber1(ref Test test)
{
test = new Test(3);
}
public void ChangeNumber2(ref Test test)
{
test.number = 4;
}
}
public class MyApp
{
public static void Main()
{
Vector3 test = new Vector3(7.0f);
ChangeVector1(ref test);
System.Console.WriteLine(test); // gibt {X:3 Y:3 Z:3} aus
ChangeVector2(ref test);
System.Console.WriteLine(test); // gibt auch {X:4 Y:4 Z:4} aus
}
public void ChangeVector1(ref Vector3 vector)
{
vector = new Vector3(3.0);
}
public void ChangeVector2(ref Vector3 vector)
{
vector.x = 4.0;
vector.y = 4.0;
vector.z = 4.0;
}
}
Call by reference funktioniert für Structs genauso wie für Klassen. Structs lassen sich somit effizienter übergeben, da keine kompletten Kopien mehr erstellt werden müssen. Allerdings muss man aufpassen dass man beim Ändern einer so übergebenen Struct direkt das ursprüngliche Objekt ändert, was standardmäßig nicht der Fall ist.
Gerade an Codestellen die häufig durchlaufen werden empfielt es sich im Hinblick auf die Performance das call by reference zu nutzen. Die häufig verwendeten Structs wie z.B. die Vector3-Struct verfügen selber bereits über entsprechende statische Methoden für verschiedene Rechenoperationen wie z.B.:
void Vector3.Add(ref Vector3 value1, ref Vector3 value2, out Vector3 result);
void Vector3.Multiply(ref Vector3 value1, float scaleFactor, out Vector3 result);
...
Diese sind wesentlich Effizienter wie ihre pendants mit call by value:
Vector3 sum = Vector3.Add(Vector3 value1, Vector2 value2);
Vector3 scaledVec = Vector3.Multiply(Vector3 value1, float scaleFactor);
...
Vector3 sum = vector1 + vector2;
Vector3 scaledVec = vector1 * scaleFactor;
...
Das out Schlüsselwort das in den Beispielmethoden ebenfalls verwendet wird dient zum deklarieren von Rückgabeparametern. Diese werden im nächsten Abschnitt erläutert.