Quand sont les structures de la réponse?
Je suis en train de faire un raytracer projet de loisirs, et à l'origine j'ai été en utilisant les structures pour mon Vecteur et Ray objets, et j'ai pensé à un raytracer est la situation parfaite pour les utiliser: vous créer des millions d'entre eux, ils ne vivent pas plus longtemps que d'une seule méthode, ils sont légers. Cependant, en changeant simplement 'struct' à 'classe' sur le Vecteur et Ray, j'ai eu un gain de performance significatif.
Ce qui donne? Ils sont tous les deux petits (3 flotteurs pour les Vecteurs 2 Vecteurs pour les Rayons), de ne pas avoir copié autour de trop. Je ne les transmettre à des méthodes si nécessaire bien sûr, mais c'est inévitable. Alors, quelles sont les erreurs les plus communes que tuer des performances lors de l'utilisation de structures? J'ai lu cette article MSDN qui dit la chose suivante:
Lorsque vous exécutez cet exemple, vous verrez que la structure de la boucle est un ordre de grandeur plus rapide. Cependant, il est important de se méfier de l'utilisation de ValueTypes lorsque vous les traiter comme des objets. Cela ajoute plus de boxing et unboxing surcharge de votre programme, et peut finir par vous coûter plus que ce qu'il aurait fait si vous aviez coincé avec des objets! Pour le voir en action, modifiez le code ci-dessus pour utiliser un tableau de foos et des bars. Vous verrez que la performance est plus ou moins égale.
Il est cependant assez ancien (2001) et de l'ensemble "les mettre dans un tableau les causes boxing/unboxing" m'a frappé comme étrange. Est-ce vrai? Cependant, je n'ai pré-calculer la primaire de rayons et de les mettre dans un tableau, j'ai pris sur cet article et calculé la primaire ray quand j'en avais besoin et jamais ajouté à un tableau, mais ça n'a rien changé: les classes, il était encore 1,5 x plus rapide.
Je suis en cours d'exécution .NET 3.5 SP1 qui, je crois, correction d'un problème où struct méthodes n'étaient pas toujours en-alignés, donc ça ne peut pas être non plus.
Donc en gros: des conseils, des choses à considérer et comment l'éviter?
EDIT: Comme l'a suggéré dans certaines réponses, j'ai mis en place un projet de test où j'ai essayé en passant des structures comme la réf. Les méthodes pour l'ajout de deux Vecteurs:
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);
}
Pour chaque j'ai eu une variation de la suite de méthode de référence:
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.
}
Tous semblent effectuer à peu près identiques. Est-il possible qu'ils soient optimisés par le JIT à ce qui est le meilleur moyen de passer cette structure?
EDIT2: je dois notons au passage que l'utilisation de structures dans mon projet de test est environ 50% plus rapide que l'utilisation d'une classe. Pourquoi est-ce différent pour mon raytracer je ne sais pas.
- Bonne chance avec le projet, d'un lancer de rayons est quelque chose que je vais attaquer bientôt.
- Voir aussi stackoverflow.com/questions/521298/when-to-use-struct-in-c/... (surtout ma réponse là 🙂 )
- La création d'un raytracer est un beaucoup de plaisir. Je trouve cela fascinant, vous pouvez créer une image à partir de rien de plus qu'un tas de chars et relativement simple vecteur de mathématiques.
- Je ne pense pas que l'article dit que le fait de mettre les structures dans un tableau des causes de la boxe. Elle a averti que l'utilisation dans des endroits où les objets sont attendus cause de boxe; par exemple, si vous passez à une méthode attend un argument de type object.
- double possible de Quand utiliser struct en C#?
Vous devez vous connecter pour publier un commentaire.
Fondamentalement, ne pas les faire trop gros, et de les transmettre autour de par réf quand vous le pouvez. J'ai découvert ce exactement de la même manière... En changeant mon Vecteur et Ray classes de structures.
Avec plus de mémoire étant passé autour, il est lié à provoquer cache raclée.
readonly
emplacement de stockage). Les Classes peuvent avoir des avantages dans certains contextes, mais lorsque ces avantages ne sont pas nécessaires, les structures passés par référence à gagner.Rectangle
, le code qui utilise cette variable sais que c'est unRectangle
; de même, si une classe a un champ de ce type, le code qui utilise ce champ de savoir de quel type il est. Le passage d'un struct commeList<int>.Enumerator
de code...IEnumerator<int>
va boîte, parce que le code qui est à l'aide de l'agent recenseur ne serait pas autrement avons aucun moyen de savoir que la chose qu'il a été donné était unList<int>.Enumerator
. Si, au contraire, on devait écrire une méthode commevoid UseEnumerator<T>(ref T theEnumerator) where T:IEnumerator<int>
et l'a appelé avec unList<int>.Enumerator
, le système permettrait de générer une version spéciale deUseEnumerator<List<int>.Enumerator()
, qui pouvait savoir que son paramètre est de type exact; étant donné que le code serait de connaître le type exact, pas de boxe serait nécessaire.Un tableau de struct serait une seule et unique structure contiguë en mémoire, bien que les éléments d'un tableau d'objets (instances de types de référence) doivent être adressés individuellement par un pointeur (c'est à dire une référence à un objet sur les déchets collectés sur le tas). Donc si vous vous adressez à de grandes collections d'éléments à la fois, des structs vous donnera un gain de performance, car ils ont besoin de moins de indirections. En outre, les structures ne peuvent pas être héritée, ce qui pourrait permettre au compilateur de faire des optimisations supplémentaires (mais ce n'est qu'une possibilité et dépend du compilateur).
Cependant, les structures très différentes d'attribution de la sémantique et aussi ne peut pas être héritée. Par conséquent, je l'habitude d'éviter les structs, sauf pour des raisons de performances en cas de besoin.
struct
Un tableau de valeurs v codée par un struct (valeur type) ressemble à ceci en mémoire:
vvvv
classe
Un tableau de valeurs v codée par une classe (type de référence) ressembler à ceci:
pppp
..v..v...v. v..
où p est la ce des pointeurs ou des références, qui pointent vers les valeurs réelles v sur le tas. Les points indiquent d'autres objets qui peuvent être intercalés sur le tas. Dans le cas de types de référence, vous devez référence v via la p correspondante, dans le cas de types de valeur, vous pouvez obtenir la valeur directement via son décalage dans le tableau.
Dans les recommandations pour l'utilisation d'un struct il dit qu'il ne devrait pas être de plus de 16 octets. Votre Vecteur est de 12 octets, ce qui est proche de la limite. Le Rayon a deux Vecteurs, le mettant au 24 octets, ce qui est clairement au-dessus de la limite recommandée.
Quand une structure est importante, plus de 16 octets, il ne peut plus être copiées de manière efficace avec un seul ensemble d'instructions, au lieu d'une boucle est utilisée. Donc, par passage de cette "magie" de la limite, vous êtes vraiment faire beaucoup plus de travail quand vous passez une struct que lorsque vous passez une référence à un objet. C'est pourquoi le code est plus rapide avec des classes eventhough il n'y a plus de surcharge lors de l'attribution des objets.
Le Vecteur peut encore être un struct, mais le Rayon est tout simplement trop grand pour une struct.
ref
au lieu de cela chaque fois que possible) même un 100 octets struct peut faire mieux que de 100 octets de classe.someProc(ref myStruct);
, la procédure sera capable de mutermyStruct
seulement jusqu'à ce qu'il retourne. En revanche, si une mutable objet de classe est jamais exposée à un code externe, il n'y a aucun moyen de savoir quand ce code risquent de provoquer un changement.Rien écrit au sujet de la boxing/unboxing avant .NET, les génériques peuvent être prises avec quelque chose d'un grain de sel. Générique types de collection ont supprimé la nécessité d'boxing et unboxing de types de valeur, ce qui rend l'utilisation des structures dans ces situations le plus précieux.
Que pour vos ralentissement, nous aurions probablement besoin de voir un peu de code.
Je pense que la clé réside dans ces deux états à partir de votre poste:
et
Maintenant, à moins que votre structure est inférieure ou égale à 4 octets la taille (ou 8 octets si vous êtes sur un système 64-bit) vous copiez beaucoup plus sur chaque appel de méthode, et puis si vous avez tout simplement passé à un objet de référence.
La première chose que je veux le regarder pour se assurer que vous avez explicitement mise en œuvre d'égal à Égal et GetHashCode. Sinon, cela signifie que l'exécution de la mise en œuvre de chacun de ces fait du très coûteuses opérations de comparer deux struct instances (en interne, il utilise la réflexion pour déterminer chacun des champs privés et ensuite checkes pour l'égalité, ce qui provoque un important montant de l'allocation).
En général, cependant, la meilleure chose que vous pouvez faire est d'exécuter votre code en vertu d'un générateur de profils et de voir où les parties lentes sont. Il peut être une expérience révélatrice.
Avez-vous présenté la demande? Le profilage est la seule sûr moyen de voir où le rendement réel problème. Il y a des opérations qui sont généralement mieux/pire sur les structures, mais à moins que vous profil vous venais de deviner ce que le problème est.
Tandis que la fonctionnalité est similaire, les structures sont généralement plus efficace que de classes.
Vous devez définir une structure, plutôt que d'une classe, si le type se comme un type de valeur qu'un type de référence.
Plus précisément, les types de structure doit répondre à tous ces critères:
- Je utiliser les structures de coeur pour le paramètre d'objets, de retour de plusieurs éléments d'information à partir d'une fonction, et... rien d'autre. Ne sais pas si elle est "bonne" ou "mauvaise", mais qu'est ce que je fais.
Mes propres ray-traceur utilise également struct Vecteurs (mais pas de Rayons) et la modification d'un Vecteur de classe ne semble pas avoir d'impact sur les performances. Je suis actuellement à l'aide de trois doubles pour le vecteur de sorte qu'il pourrait être plus grand qu'il ne devrait l'être. Une chose à noter, cependant, et cela peut être évident, mais ce n'était pas pour moi, et qui consiste à exécuter le programme en dehors de visual studio. Même si vous choisissez de la version optimisée de construire, vous pouvez obtenir un énorme boost de vitesse si vous lancez l'exe en dehors de VS. Toute analyse comparative vous ne devriez prendre cela en considération.
myVec.x = expr1; myVec.y = expr2; myVec.z = expr3;
est apte à prendre la même quantité de temps que juste mise en place des paramètres pour un appel de constructeur. L'appel lui-même, et le temps passé dans le constructeur, représenterait pur gaspillage de frais généraux. De même, tandis que il ya des moments où la Gigue peut optimiser une propriété en lecture demyPoint.x
qui lit simplement un champ de stockage_x
, il existe de nombreux cas où la Gigue ne sera pas.Si les structures sont de petite taille, et pas trop d'exister à la fois, il convient de les placer sur la pile (aussi longtemps qu'une variable locale et non pas un membre d'une classe) et non pas sur le tas, cela signifie que la GC n'a pas besoin d'être invoquée et allocation/libération de mémoire devrait être quasi instantanée.
Lors du passage d'une structure en tant que paramètre à la fonction, la structure est copié, ce qui ne signifie pas seulement plusieurs allocations/deallocations (à partir de la pile, qui est presque instantané, mais il a encore les frais généraux), mais au-dessus de la tête du transfert des données entre les 2 exemplaires. Si vous passez par référence, ce n'est pas un problème tant que vous êtes seulement de leur dire où lire les données à partir, plutôt que de copier.
Je ne suis pas sûr à 100%, mais je soupçonne que le retour des tableaux via un paramètre peut également vous donner un boost de vitesse, comme la mémoire sur la pile est réservé et n'a pas besoin d'être copié en tant que la pile est "déroulé" à la fin des appels de fonction.
Vous pouvez également faire des structs en Nullable objets. Classes personnalisées ne seront pas en mesure de créé
comme
où avec une struct est nullable
Mais vous sera (évidemment) de perdre tout votre héritage caractéristiques