Frage

Ich bin ein Raytracer Hobby-Projekt zu tun, und ursprünglich war ich structs für meinen Vektor und Ray Objekte verwenden, und ich dachte, ein Raytracer die perfekte Lage war, sie zu benutzen: Sie Millionen von ihnen zu schaffen, sie leben länger nicht als eine einzige Methode, sind sie leicht. Allerdings, indem Sie einfach ‚struct‘ to ‚Klasse‘ auf Vektor und Ray zu ändern, habe ich eine sehr deutliche Leistungssteigerung.

Was soll das? Sie sind beide klein (3 Schwimmer für Vector, 2 Vektoren für eine Ray), nicht übermäßig kopiert umgehen. Ich tun, um sie an Methoden übergeben, wenn natürlich erforderlich, aber das ist unvermeidlich. Also, was sind die häufigsten Fallen, die Leistung zu töten, wenn structs mit? Ich habe gelesen diese MSDN-Artikel, die Folgendes sagt:

  

Wenn Sie dieses Beispiel ausführen, werden Sie sehen, dass die Struktur Schleife um Größenordnungen schneller. Allerdings ist es wichtig, mit ValueTypes zu passen, wenn man sie wie Objekte zu behandeln. Dies sorgt für zusätzliche Boxen und Unboxing-Overhead zu Ihrem Programm, und Sie mehr als es kann am Ende kostet würde, wenn Sie mit Objekten stecken haben! Um dies zu sehen in Aktion, ändern Sie den Code über eine Reihe von Foos und Bars zu verwenden. Sie werden feststellen, dass die Leistung mehr oder weniger gleich ist.

Es ist jedoch ziemlich alt (2001) und das ganze „sie in einem Array setzen verursacht Boxen / Unboxing“ kam mir seltsam. Ist das wahr? Aber ich habe die Primärstrahlen vorge berechnen und sie in einem Array setzen, so habe ich zu diesem Artikel und den Primärstrahl berechnet, wenn ich sie brauchte und nie gegeben, um sie zu einem Array, aber es hat nichts ändern: mit Klassen, wurde 1,5x noch schneller.

I .NET 3.5 SP1 bin mit dem ich ein Problem behoben glauben, wo struct Methoden nicht immer waren in ausgekleideten, so dass es auch nicht sein kann.

Also im Grunde: alle Tipps, Dinge zu beachten und was zu vermeiden

EDIT: Wie in einigen Antworten vorgeschlagen, ich habe ein Testprojekt eingerichtet, wo ich beiläufig structs als ref versucht haben. Die Methoden für das Hinzufügen von zwei Vektoren:

public static VectorStruct Add(VectorStruct v1, VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static VectorStruct Add(ref VectorStruct v1, ref VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static void Add(ref VectorStruct v1, ref VectorStruct v2, out VectorStruct v3)
{
  v3 = new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

Für jede bekam ich eine Variation der folgenden Benchmark-Methode:

VectorStruct StructTest()
{
  Stopwatch sw = new Stopwatch();
  sw.Start();
  var v2 = new VectorStruct(0, 0, 0);
  for (int i = 0; i < 100000000; i++)
  {
    var v0 = new VectorStruct(i, i, i);
    var v1 = new VectorStruct(i, i, i);
    v2 = VectorStruct.Add(ref v0, ref v1);
  }
  sw.Stop();
  Console.WriteLine(sw.Elapsed.ToString());
  return v2; // To make sure v2 doesn't get optimized away because it's unused. 
}

Alle scheinen ziemlich identisch auszuführen. Ist es möglich, dass sie von der JIT optimieren bekommen, was ist der optimale Weg, um diese Struktur zu übergeben?

EDIT2: Ich muss durch die Art und Weise beachten Sie, dass structs in meinem Testprojekt etwa 50% schneller als mit einer Klasse. Warum dies für meinen Raytracer anders ist weiß ich nicht.

War es hilfreich?

Lösung

Im Grunde lassen Sie sie nicht zu groß, und geben sie um durch ref, wenn Sie können. Ich entdeckte dies genau die gleiche Art und Weise ... meine Vector und Ray Klassen Mit dem Wechsel Structs.

Mit mehr Speicher um übergeben wird, wird es gebunden Cache Dreschen verursachen.

Andere Tipps

Ein Array von Strukturen wäre eine einzige zusammenhängende Struktur im Speicher, während Elemente in einem Array von Objekten (Instanzen von Referenztypen) müssen einzeln auf dem Garbage Collected Heap auf ein Objekt durch einen Zeiger (dh eine Referenz angesprochen werden ). Deshalb, wenn Sie auf einmal große Sammlungen von Gegenständen befassen, Strukturen erhalten Sie eine Leistungssteigerung geben, da sie weniger Indirekt brauchen. Darüber hinaus structs kann nicht vererbt werden, was der Compiler zusätzliche Optimierungen machen lassen könnte (aber das ist nur eine Möglichkeit, und hängt von der Compiler).

Allerdings structs haben ganz unterschiedliche Zuordnung Semantik und auch nicht vererbt werden kann. Deshalb würde ich in der Regel structs mit Ausnahme der gegebenen Leistungsgründen vermeiden, wenn nötig.


struct

ein Array von Werten durch ein struct (value-Typ) kodiert v wie folgt aussieht Speicher:

vvvv

Klasse

ein Array von Werten durch eine Klasse (Referenz-Typ) kodiert v wie folgt aussehen:

pppp

.. v..v ... V.v ..

, wo p die diese Zeiger oder Referenzen, die zeigen Sie auf den tatsächlichen Werten auf dem Heap v. Die Punkte zeigen andere Objekte, die auf dem Heap durchsetzt sein kann. Im Fall von Referenztypen müssen Sie v über die entsprechenden p, im Fall von Werttypen verweisen Sie den Wert in dem Feld seine Offset bekommen können direkt über.

In den Empfehlungen für, wenn eine Struktur zu verwenden, es sagt, dass es nicht größer als 16 Bytes sein sollte. Ihr Vector ist 12 Bytes, die bis an die Grenze der Nähe ist. Der Strahl hat zwei Vektoren, es bei 24 Bytes setzen, die deutlich über den empfohlenen Grenzwert ist.

Wenn eine Struktur größer als 16 Bytes bekommt kann sie nicht mehr effizient mit einem einzigen Satz von Befehlen kopiert werden, statt eine Schleife verwendet wird. Also, indem man diese „magische“ Grenze, machst du eigentlich viel mehr Arbeit, wenn Sie eine Struktur als übergeben, wenn Sie einen Verweis auf ein Objekt übergeben. Aus diesem Grunde ist der Code schneller mit Klassen obwohl es mehr Aufwand, wenn die Objekte zugeordnet werden.

Der Vektor könnte noch eine Struktur sein, aber der Ray ist einfach zu groß und eine Struktur zu arbeiten.

Alles, was geschrieben Boxen in Bezug auf / Unboxing vor .NET Generika können mit etwas von einem Körnchen Salz genommen werden. Generisches Sammlungstypen haben die Notwendigkeit für Boxen und Unboxing von Werttypen entfernt, die wertvollere Verwendung structs in diesen Situationen macht.

Wie für Ihre spezifische Verlangsamung -. Wir würden wahrscheinlich einige Codes sehen müssen

Ich denke, der Schlüssel liegt in diesen beiden Aussagen aus Ihrem Post:

  

Sie schaffen Millionen von ihnen

und

  

ich geben sie nicht an Methoden, wenn natürlich erforderlich

Nun, es sei denn Ihre Struktur ist kleiner als oder gleich 4 Bytes groß (oder 8 Bytes, wenn Sie auf einem 64-Bit-System) sind Sie kopieren viel mehr auf jeden Methodenaufruf dann, wenn Sie einfach einen Objektverweis übergeben.

Das erste, was ich sehen würde, ist sicherzustellen, dass Sie explizit Equals und GetHashCode umgesetzt haben. Geschieht dies nicht zu tun, bedeutet, dass die Laufzeitimplementierung eines jeden dieser hat einige sehr teure Operationen zwei struct Instanzen zu vergleichen (intern verwendet es Reflexion jedes der privaten Felder zu bestimmen und dann checkes sie für Gleichheit, bewirkt dies eine erhebliche Menge an Zuteilung) .

Generell ist aber das Beste, was Sie tun können, ist der Code unter einem Profiler laufen und sehen, wo die langsamen Teile sind. Es kann ein Aha-Erlebnis.

Haben Sie die Anwendung profiliert? Profilieren ist der einzige sichere Weg, um zu sehen, wo das tatsächliche Performance-Problem ist. Es gibt Vorgänge, die im Allgemeinen besser / schlechter auf structs sind aber, wenn Sie das Profil würden Sie genauso zu erraten, was das Problem ist.

Während die Funktionalität ähnlich ist, Strukturen sind in der Regel effizienter als Klassen. Sie sollten eine Struktur, anstatt eine Klasse, definieren, wenn der Typ wird besser durchführen als Werttyp ist als ein Referenztyp.

Im einzelnen Strukturtypen sollen alle diese Kriterien erfüllen:

  • stellt Logischer ein einzelner Wert
  • Hat eine Instanz Größe von weniger als 16 Bytes
  • Wird nach der Erstellung nicht geändert werden
  • Wird nicht auf einen Referenztyp umgewandelt werden

Ich verwende structs grundsätzlich für Parameterobjekte, mehrere Informationen aus einer Funktion zurückkehrt, und ... nichts anderes. Sie wissen nicht, ob es „richtig“ oder „falsch“ ist, aber das ist, was ich tue.

Mein eigenes Raytracing verwendet auch struct Vektoren (wenn auch nicht Rays) und Vector in der Klasse zu ändern scheint nicht auf der Leistung keine Auswirkungen zu haben. Ich bin derzeit mit drei Doppelzimmern für den Vektor so könnte es sein, größer als es sein sollte. Eine Sache zu beachten aber, und dies mag offensichtlich sein, aber es war nicht für mich, und das ist das Programm außerhalb von Visual Studio laufen. Auch wenn Sie es optimierte Release-Build festlegen können Sie eine massive Geschwindigkeitsschub erhalten, wenn Sie die EXE außerhalb von VS. starten Alle Benchmarking Sie tun sollten dies berücksichtigen.

Wenn die Strukturen sind klein und nicht zu viele auf einmal vorhanden sind, sollten sie sie auf den Stapel platzieren (solange es ist eine lokale Variable und kein Mitglied einer Klasse) und nicht auf dem Heap, dies bedeutet, dass die GC benötigt keine Freigabe aufgerufen und Speicherzuweisung / werden sollte fast augenblicklich sein.

Wenn eine Struktur als Parameter der Struktur zu funktionieren, kopiert wird, die nicht nur mehr Zuweisungen / Zuordnungsaufhebungen bedeutet (aus dem Stapel, die fast augenblicklich ist, aber immer noch über Kopf), aber der Aufwand in nur Daten übertragen werden zwischen die 2 Kopien. Wenn Sie über Verweis übergeben, das ist kein Thema, wie Sie es nur zu sagen, wo die Daten lesen aus, anstatt es zu kopieren.

Ich bin nicht 100% sicher, auf diesem, aber ich vermute, dass Arrays über eine ‚out‘ Parameter der Rückkehr können Sie auch einen Geschwindigkeitsschub geben, als Speicher auf dem Stack ist für sie reserviert und muss nicht kopiert werden wie der Stapel „abgewickelt“ am Ende der Funktionsaufrufe.

Sie können auch structs in Nullable Objekte. Benutzerdefinierte Klassen werden nicht in der Lage sein, erstellt

als

Nullable<MyCustomClass> xxx = new Nullable<MyCustomClass>

, wo mit einer Struktur ist Nullable

Nullable<MyCustomStruct> xxx = new Nullable<MyCustomStruct>

Aber Sie werden (natürlich) verlieren alle Ihre Vererbungsmerkmale

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top