Laden...

Was ist eigentlich Casting?

Erstellt von MrSparkle vor 16 Jahren Letzter Beitrag vor 16 Jahren 8.590 Views
MrSparkle Themenstarter:in
5.658 Beiträge seit 2006
vor 16 Jahren
Was ist eigentlich Casting?

Hallo allerseits,

ich möchte mal eine Frage stellen, die weniger mit "wie geht etwas" zu tun hat, sondern eher mit "warum ist das so".

Wie die Betreffzeile verrät, geht es um das Casten von Typen. Im Forum (und anderswo) wurde desöfteren erwähnt, daß Casting sehr zeitaufwändig ist und deshalb so wenig wie möglich eingesetzt werden sollte.

Im einfachsten Fall ist es eine Umwandlung eines Rechenergebnisses in den Zieltyp, z.B.


float angle = 0.5f;
float sin = (float)Math.Sin(angle);

Dabei wird zweimal gecastet, einmal weil Math.Sin ein double erwartet (implizite Konvertierung) und dann wieder zurück, weil das Ergebnis von double in float umgewandelt wird (explizit). Das kann man halt nicht verhindern, aber ist die Konvertierung zwischen float und double wirklich rechenaufwändig?

Ein anderes Beispiel sind die EventHandler:


private void PictureBox1_Click(object sender, EventArgs e)
{
  ((PictureBox)sender).ErrorImage = myErrorImage;
}

Und ein ganz krassen Beispiel ist der Code für das Dot-Produkt eines Vektors in DirectX:


public static unsafe float Dot(Vector3 left, Vector3 right)
{
    return (((*(((float*) (&right + 8))) * *(((float*) (&left + 8)))) + (*(((float*) (&right + 4))) * *(((float*) (&left + 4))))) + (*(((float*) &right)) * *(((float*) &left))));

  // Anstatt:
  return (right.Z * left.Z) + (right.Y * left.Y) + (right.X + left.X);
}

Leider hab ich nicht die geringste Vorstellung davon, was während einer Typkonvertierung im Code alles abläuft. Ich könnte mir vorstellen, daß die drei o.a. Beispiele sehr unterschiedlich funktionieren und man deshalb die allgemeine Aussage "Casting macht die Performance kaputt" etwas differenzierter betrachten könnte.

Deshalb hab ich ein paar Fragen dazu:

  • Wie funktioniert eine Typenumwandlung?
  • Funktioniert diese anders für Primitive Datentypen als für Instanzen oder Pointer?
  • Gibt es einen Unterschied zwischen impliziter und expliziter Konvertierung (auf Maschinensprachebene)?
  • Ist eine Typkonvertierung das gleiche wie Casting?
  • Wo kann man darauf verzichten, bzw. wie kann man es performanter gestalten? Beim dritten Beispiel scheint man ja gerade aus Performancegründen die Werte als float* zu casten.

Bin euch dankbar für jede Art der Aufklärung!
Christian

Weeks of programming can save you hours of planning

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo MrSparkle,

Das kann man halt nicht verhindern, aber ist die Konvertierung zwischen float und double wirklich rechenaufwändig?

also ich halte Casten nicht für aufwändig. Ich hatte noch nie Performance-Probleme, weil ich gecastet habe.

  • Wie funktioniert eine Typenumwandlung?

Unterschiedlich. 🙂


small s = (small)i; // hier werden einfach die 2 niederwertigsten bytes zugewiesen
float f = (float)d; // hier muss den float Wert richtig berechnet werden.
Button b = (Button)sender; // hier wird nur geguckt, ob der Typ passt
                           // und dann wird normal die Referenz zugewiesen

  • Funktioniert diese anders für Primitive Datentypen als für Instanzen oder Pointer?

Ja, s.o.

  • Gibt es einen Unterschied zwischen impliziter und expliziter Konvertierung (auf Maschinensprachebene)?

Nein!

  • Ist eine Typkonvertierung das gleiche wie Casting?

Nein, nicht unbedingt. Mit Convert werden auch Konvertierungen vorgenommen, ohne das das ein Cast ist.

  • Wo kann man darauf verzichten, bzw. wie kann man es performanter gestalten?

Du musst gar nicht darauf verzichten. Da wo es nötig ist, caste eben. Wenn man auf Casts verzichten will, dann eher weil man sich damit potentiell eine Exception einfängt, wenn das vom Cast betroffene Objekt nicht von einem passenden Typ ist. Deshalb sollte man z.B. besser die generischen (List<T>) statt der untypsierten (ArrayList) Collections verwenden.

herbivore

F
10.010 Beiträge seit 2004
vor 16 Jahren

Und nicht zu vergessen bei objectcasts lieber den dynamischen verwenden.


private void PictureBox1_Click(object sender, EventArgs e)
{
  PictureBox pb = sender as PictureBox;
  if( pb != null)
    pb.ErrorImage = myErrorImage;
} 

Hilft die eine oder andere Exception zu vermeiden.

343 Beiträge seit 2007
vor 16 Jahren

Original von MrSparkle
Wo kann man darauf verzichten, bzw. wie kann man es performanter gestalten?

Ich habe es zwar noch nie selbst getestet, aber angeblich soll die benutzung von generischen Listen oder Funktionen schon um einiges schneller (angeblich bis zu 10 mal so schnell) sein, als normale Collections, bei denen gecastet wird.
Wäre interessant das ganze mal mit einem kleinen Tool zu testen.

Mfg Preli

[- www.saftware.net -](http://www.saftware.net/)
X
40 Beiträge seit 2005
vor 16 Jahren

Bei ValueTypes hat das weniger mit dem Casten zu tun, als mit dem Boxing bzw. Unboxing welches beim Einfügen und Auslesen aus einer (z.B.) ArrayList stattfindet.

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo zusammen

Nette und interessante Diskussion.
Bei einer meiner MDX (Managed DirectX) Anwendungen habe ich massive Performanceunterschiede durch "unnötiges" Casting gemessen.
Ich würde aber sagen, bei normalen Anwendungen kann man diese Sache relativ sehen.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

MrSparkle Themenstarter:in
5.658 Beiträge seit 2006
vor 16 Jahren

Hallo & vielen Dank für die vielen interessanten Beiträge. Mich würde mal interessieren, wie das ganze funktioniert. Wenn ein Objekt gecastet werden soll, muß die Laufzeitumgebung ja ersteinmal feststellen, ob der Quell- und Zieltyp überhaupt miteinander kombatibel sind. Das stell ich mir so ähnlich vor, wie beim Arbeiten mit Reflection. Aber wie weit geht das denn?
Wenn ich das richtig in Erinnerung habe, erzeugt der Compiler ja aus dem C# Code eine .NET-Zwischensprache, die auf dem Zielsystem in die "richtige" Maschinensprache für den jeweiligen Prozessor übersetzt wird. An welcher Stelle werden dann aus den Objekten einfache Speicherzugriffe?

Und nicht zu vergessen bei objectcasts lieber den dynamischen verwenden.

Was ist der Unterschied zwischen "(object)number" und "number as object", außer daß ersteres im Zweifelsfall eine Exception auslöst und letzteres null zurückgibt? Gibt es hier Performance-Unterschiede (wenn keine Exception ausgelöst wird)?

Bei ValueTypes hat das weniger mit dem Casten zu tun, als mit dem Boxing bzw. Unboxing welches beim Einfügen und Auslesen aus einer (z.B.) ArrayList stattfindet.

Was bedeutet Boxing/Unboxing, das Casten von Werttypen?

Weeks of programming can save you hours of planning

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo MrSparkle,

Aber wie weit geht das denn?

naja, es passiert eben genau das, was du sagst. Die Typen werden per Reflection verglichen.

An welcher Stelle werden dann aus den Objekten einfache Speicherzugriffe?

Auf Maschinensprachebene.

Was ist der Unterschied zwischen "(object)number" und "number as object", außer daß ersteres im Zweifelsfall eine Exception auslöst und letzteres null zurückgibt?

Schau bitte in die FAQ: [Tipp] Casten aber richtig: Begriffe wie Cast / is / as

Was bedeutet Boxing/Unboxing, das Casten von Werttypen?

Bitte schlage allgemeine Begriffe in der :rtfm: Doku, in Wikipedia, in Google oder Co nach.

herbivore

MrSparkle Themenstarter:in
5.658 Beiträge seit 2006
vor 16 Jahren

Hi herbivore,

danke, es ist ja nicht so, daß ich nicht wüßte was damit gemeint ist und wie man es anwendet. Mich interessiert mehr, wie es funktioniert.

Christian

// Edit:

Ich habe mal einen Test geschrieben. Das Casting scheint doch relativ aufwändig zu sein. Versuchsweise hab ich jeweils eine List<object> und eine List<string> mit 5 Mio Strings gefüllt und diese wieder in eine String-Variable ausgelesen (und dabei gecastet).
Beim Füllen der Listen war die List<string> ganz knapp im Vorteil, beim Auslesen hat allerdings das Casten zwischen 50 und 80% der Rechenzeit ausgemacht.

Weeks of programming can save you hours of planning

S
8.746 Beiträge seit 2005
vor 16 Jahren

Am besten mal IL und Assembler-Code anschauen.

Ganz interessant: Die Auszeichnung einer Konstante mittels "L" als long ist völlig ohne Effekt. Der Compiler schaut auf die Größe und legt sie immer als kleinstmöglichen Typ (z.B. int32) an, um sie dann mittels IL-Instruktion "conv" in den jeweiligen "Zieltyp" zu casten. Der JIT wiederum ignoriert "conv" und schiebt mittels move ein 64-Bit-Register in ein 32-Bit-Register, welches zum Verlust der oberen 32 Bit führt.

Bei Referenztypen wird der IL-Code "castclass" aufgerufen.

383 Beiträge seit 2006
vor 16 Jahren

Original von MrSparkle
danke, es ist ja nicht so, daß ich nicht wüßte was damit gemeint ist und wie man es anwendet. Mich interessiert mehr, wie es funktioniert.

Hallo MrSparkle

Ich kann dir dieses Buch empfehlen:
http://www.amazon.de/Microsoft-NET-Framework-Programmierung-Expertenwissen-Framework/dp/3860639846

Darin wirst du noch jede Menge andere gute Infos über die Funktionsweise der CLR finden.

Gruss
wakestar

MrSparkle Themenstarter:in
5.658 Beiträge seit 2006
vor 16 Jahren

Da ich von CLR, IL, JIT etc. keine Ahnung habe, sind die 50 Euro sicher gut angelegt. Werd mir das Buch gleich bestellen, hab vielen Dank für den Tip!

Weeks of programming can save you hours of planning

0
767 Beiträge seit 2005
vor 16 Jahren

nicht zu vergessen, dass man mittels

implicit operator
explicit operator

auch selber noch umwandlungsmethoden schreiben kann... ist aber mit vorsicht zu geniessen.

loop:
btst #6,$bfe001
bne.s loop
rts

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo MrSparkle,

Beim Füllen der Listen war die List<string> ganz knapp im Vorteil, beim Auslesen hat allerdings das Casten zwischen 50 und 80% der Rechenzeit ausgemacht.

das mag schon sein und ich empfehle ja auch immer, möglichst die generischen Collections zu verwenden. Aber ich kann mich nur wiederholen:

Ich hatte noch nie Performance-Probleme, weil ich gecastet habe.

und noch ein

Premature optimization is the root of all evil

hinzufügen.

Natürlich kann es sein, dass man in irgendeiner inneren Schleife, die milliardenmal durchlaufen wird, tatsächlich durch Vermeiden eines Casts einen spürbaren Effekt bewirken kann, aber zum einen wird so eine Situation selten auftreten und zum anderen kann man dann immer noch gezielt die eine Stelle ändern und muss sich nicht in den anderen 99,999% des Codes unnötig Gedanken über das Vermeiden von Casts machen.

herbivore

MrSparkle Themenstarter:in
5.658 Beiträge seit 2006
vor 16 Jahren

Original von 0815Coder
nicht zu vergessen, dass man mittels

implicit operator  
explicit operator  

auch selber noch umwandlungsmethoden schreiben kann... ist aber mit vorsicht zu geniessen.

Warum mit Vorsicht zu genießen? Ich selber verwende die impliziten Umwandlungs-Operatoren, um bspw. meine eigene (u.a. serialisierbare) Color-Struktur auch an Funktionen übergeben zu können, die ein Argument vom Typ Drawing.Color erwarten. Das gleiche ist auch hilfreich bei einigen DirectX-Datentypen.

@herbivore:

Natürlich kann es sein, dass man in irgendeiner inneren Schleife, die milliardenmal durchlaufen wird, tatsächlich durch Vermeiden eines Casts einen spürbaren Effekt bewirken kann, aber zum Einen wird so eine Situation selten auftreten und zum Anderen kann man dann immer noch gezielt die eine Stelle ändern und muss sich nicht in den anderen 99,999% des Codes unnötig Gedanken über das Vermeiden von Casts machen.

Ja, ich hätte evtl. erwähnen können, daß es sich bei den 5 Mio Castings um 20-30 ms gehandelt hat. Das Beispiel hat einfach keinen praktischen Bezug. Ich versuche gerade, einige sehr performancebedürftige Grafik-Routinen zu optimieren, da kam ich aber ohne das Grundlagenwissen (wie etwas weshalb so funktioniert wie es funktioniert) nicht weiter.
Deshalb nochmal vielen Dank an alle, die mir weitergeholfen haben. Ich denke mal, ich werd mich erstmal mit dem erwähnten Buch beschäftigen, das wird bestimmt sehr interessant werden.

Schöne Grüße,
Christian

Weeks of programming can save you hours of planning

0
767 Beiträge seit 2005
vor 16 Jahren

Original von MrSparkle
Warum mit Vorsicht zu genießen? Ich selber verwende die impliziten Umwandlungs-Operatoren, um bspw. meine eigene (u.a. serialisierbare) Color-Struktur auch an Funktionen übergeben zu können, die ein Argument vom Typ Drawing.Color erwarten. Das gleiche ist auch hilfreich bei einigen DirectX-Datentypen.

mit "vorsicht" meinte ich eher, dass man die nicht falsch einsetzen sollte, weil vorallem der implizite eben nicht im code steht, und schwer zu ersehen ist, dass da noch was passiert. bei deinem beispiel hört sich das aber ok an.

ich verwende den implicit in einem projekt, das auf 3 webservices zugreift. in jedem der drei webservices gibt es klassen die gleich heissen, das gleiche darstellen, und überhaupt exakt das gleiche sind. nur eben in anderem namespace. für die umwandlung zwischen diesen verwende ich auch implicit.

für die cast operatoren gibts ja sehr oft die möglichkeit statt dessen eine "ToXyz()" methode zu schreiben, zumindest für die klassen auf deren code man zugreifen kann, bzw wenn man von einer klasse umwandeln will, auf die man keinen zugriff hat (wie System.Drawing.Color) kann man in seiner eigenen immer noch eine statische "FromXyz()" schreiben. das ganze ist dann zwar nur noch explizit, aber auch lesbarer, wenn eine echte umwandlung stattfindet.

loop:
btst #6,$bfe001
bne.s loop
rts