2009-05-18 7 views
12

J'écris des algorithmes qui fonctionnent sur des séries de données numériques, où parfois, une valeur dans la série doit être nulle. Cependant, étant donné que cette application est essentielle à la performance, j'ai évité l'utilisation de types nullables. J'ai perf testé les algorithmes pour comparer spécifiquement les performances de l'utilisation de types nullable vs types non nullable, et dans le meilleur des cas, les types nullables sont 2x plus lent, mais souvent bien pire.Alternatives aux types Nullable en C#

Le type de données le plus souvent utilisé est double, et actuellement l'alternative choisie à null est double.NaN. Cependant, je comprends que ce n'est pas l'utilisation exacte prévue pour la valeur NaN, donc je ne suis pas sûr s'il y a des problèmes avec cela que je ne peux pas prévoir et quelle serait la meilleure pratique.

Je suis intéressé à savoir quelles sont les meilleures alternatives nulles sont les types de données suivants en particulier: double/flotteur, décimal, DateTime, int/long (bien que d'autres sont plus que bienvenus)

Edit: I pense que je dois clarifier mes exigences en matière de performance. Les concerts de données numériques sont traités à travers ces algorithmes à un moment qui prend plusieurs heures. Par conséquent, bien que la différence entre, par exemple, 10 ms ou 20 ms soit généralement insignifiante, dans ce scénario, cela a vraiment un impact significatif sur le temps pris.

+0

Certaines informations sur le type et la quantité de données que vous attendez seraient bonnes. – peterchen

+1

Pour info - mes tests montrent que dans le cas où aucune valeur n'est nulle, Nullable et les nombres magiques sont assez égaux; quand un zéro est impliqué, oui, l'approche du nombre magique est un peu plus rapide ... mais est-ce assez rapide pour valoir le désagrément? Il est encore très, très rapide - 50M itérations en 86ms (nombre magique) vs 144ms (Nullable ) sur ma machine ... –

+0

(voir aussi ma réponse à vos tests i == null etc, le compilateur le fait déjà via " levé "opérateurs, vous êtes probablement en train de le dupliquer ...) –

Répondre

18

Eh bien, si vous avez exclu Nullable<T>, il vous reste des valeurs de domaine, c'est-à-dire un nombre magique que vous traitez comme nul. Bien que ce ne soit pas idéal, il n'est pas rare non plus - par exemple, une grande partie du code cadre principal traite DateTime.MinValue la même chose que null. Cela déplace au moins les dégâts loin des valeurs communes ...

modifier pour mettre en évidence que si aucune NaN

Alors, où il n'y a pas NaN, peut-être utiliser .MinValue - mais rappelez-vous ce que les maux se produire si vous accidentellement utiliser cette même valeur signifiant le même nombre ...

De toute évidence pour les données non signées, vous aurez besoin .MaxValue (éviter zéro!).

Personnellement, je vais essayer d'utiliser Nullable<T> comme exprimant mon intention plus en toute sécurité ... il peut y avoir des moyens d'optimiser votre code Nullable<T>, peut-être. Et aussi - au moment où vous avez vérifié le nombre magique dans tous les endroits dont vous avez besoin, il ne sera peut-être pas beaucoup plus rapide que Nullable<T>?

+0

Je suis d'accord, je pense que c'est une bien meilleure alternative que les doubles sauf si vous devez avoir long.MaxValue être valide. – BobbyShaftoe

+1

Pour les valeurs doubles ou flottantes, NaN ou l'un des infinis peut être utilisé comme valeur "nulle", si vous n'en avez pas besoin. – Joey

+0

En ce qui concerne les vérifications, les types Null nécessitent le même nombre de vérifications, où je vérifie un nombre magique, je vérifie null. Donc, les tests de performance que j'ai effectués ont pris cela en compte. Je suis d'accord que ce n'est pas idéal, mais dans ce scénario, la performance est non. 1 priorité. Et dans ce scénario, la différence de perf entre les opérations aussi simple que int + int et int? + int? est significatif. – Ryan

4

Je suis un peu en désaccord avec Gravell sur ce cas de bordure spécifique: une variable Null-ed est considérée comme 'non définie', elle n'a pas de valeur. Donc tout ce qui est utilisé pour signaler que c'est OK: même les nombres magiques, mais avec des nombres magiques, vous devez prendre en compte qu'un nombre magique vous hantera toujours dans le futur quand il devient une valeur 'valide' tout à coup. Avec Double.NaN, vous n'avez pas à avoir peur de cela: ça ne va jamais devenir un double valide. Bien que, vous devez considérer que NaN dans le sens de la séquence de doubles ne peut être utilisé comme un marqueur pour «non défini», vous ne pouvez pas l'utiliser comme un code d'erreur dans les séquences, évidemment. Donc tout ce qui est utilisé pour marquer 'indéfini': il doit être clair dans le contexte de l'ensemble de valeurs que cette valeur spécifique est considérée comme la valeur de 'non défini' ET qui ne changera pas dans le futur.

Si Nullable vous donne trop de problèmes, utilisez NaN, ou quoi que ce soit d'autre, tant que vous considérez les conséquences: la valeur choisie représente 'indéfini' et cela restera.

+0

Vous avez raison, et je n'avais pas été clair. J'avais seulement signifié le MinValue etc. pour les moments où il n'y a aucun NaN - int, long, décimal, DateTime etc. Pour le double/float, NaN est la réponse évidente (que j'avais supposé, de la question). –

2

réponse partielle:

float et double fournissent NaN (Not a Number). NaN est un peu difficile car, par spec, NaN! = NaN. Si vous voulez savoir si un nombre est NaN, vous devez utiliser Double.IsNaN(). Voir aussi Binary floating point and .NET.

+1

En aparté ... dans la plupart des bases de données, null! = Null aussi, donc ce n'est pas forcément un territoire inattendu ... mais oui: c'est différent de la façon dont C# gère l'égalité de Nullable . –

4

Je travaille sur un grand projet qui utilise NaN comme valeur null. Je ne suis pas entièrement à l'aise avec cela - pour des raisons similaires à la vôtre: ne sachant pas ce qui peut mal tourner. Nous n'avons pas rencontré de réels problèmes à ce jour, mais être conscient des éléments suivants:

NaN - Arithmétique Bien que, la plupart du temps, « NaN promotion » est une bonne chose, il pourrait ne pas toujours ce que vous attendre.

Comparaison - Comparaison des valeurs devient plutôt cher, si vous voulez que les NaN soient comparables. Maintenant, tester les flotteurs pour l'égalité n'est pas simple de toute façon, mais commander (un < b) peut devenir vraiment moche, parce que nan doit parfois être plus petit, parfois plus grand que les valeurs normales.

Code Infection - Je vois beaucoup de code arithmétique qui nécessite une manipulation spécifique de NaN pour être correct. Vous vous retrouvez donc avec des «fonctions qui acceptent NaN» et «des fonctions qui n'en ont pas» pour des raisons de performance.

Autres non-finies NaN n'est pas la seule valeur non finie. Doit être gardé à l'esprit ...

Les exceptions à virgule flottante ne posent aucun problème lorsqu'elles sont désactivées. Jusqu'à ce que quelqu'un leur permette. Histoire vraie: Intialisation statique d'un NaN dans un contrôle ActiveX. Cela ne semble pas effrayant, jusqu'à ce que vous changiez d'installation pour utiliser InnoSetup, qui utilise un noyau Pascal/Delphi (?), Dont les exceptions FPU sont activées par défaut. Ça m'a pris un moment pour comprendre. Donc, dans l'ensemble, rien de grave, bien que je préférerais ne pas avoir à considérer NaNs si souvent. J'utiliserais des types Nullable aussi souvent que possible, à moins qu'ils ne soient (prouvés être) des contraintes de performance/de ressources. Un cas peut être de grands vecteurs/matrices avec des NaN occasionnels, ou de grands ensembles de valeurs individuelles nommées où le comportement NaN par défaut est correct.


Vous pouvez également utiliser un vecteur d'index des vecteurs et des matrices, standard implémentations « matrice clairsemée », ou un bool séparé/vecteur de bits.

0

Peut-être que la diminution significative des performances se produit lors de l'appel d'un membre ou de propriétés de Nullable (boxe). Essayez d'utiliser une structure avec le double + un booléen indiquant si la valeur est spécifiée ou non.

+0

Mais les types NULL sont des structures déjà ... – Ryan

+0

C'est exactement ce que fait Nullable - c'est une structure, elle a une valeur (par exemple de type double) et un booléen indiquant que la valeur est assignée ou non. Et sans frais de boxe. –

+0

Les propriétés (HasValue et Value) sont des méthodes internes (get_HasValue et get_Value). Donc, ils sont soumis à la boxe (à condition qu'aucune magie de compilateur spécial pour Nullable se produit ici). –

0

On peut éviter une partie de la dégradation des performances associée à Nullable<T> en définissant votre propre structure

struct MaybeValid<T> 
{ 
    public bool isValue; 
    public T Value; 
} 

Si vous le souhaitez, on peut définir le constructeur, ou un opérateur de conversion de T à MaybeValid<T>, etc., mais l'utilisation excessive de ces les choses peuvent donner des performances sous-optimales. Les structures de champ exposé peuvent être efficaces si l'on évite de copier des données inutiles. Certaines personnes peuvent désapprouver la notion de champs exposés, mais elles peuvent être massivement plus efficaces que les propriétés. Si une fonction qui retourne un T doit avoir une variable de type T pour conserver sa valeur de retour, l'utilisation d'un MaybeValid<Foo> augmente simplement de 4 la taille de la chose à renvoyer. En revanche, l'utilisation d'un Nullable<Foo> nécessiterait que la fonction calcule d'abord le Foo, puis en transmette une copie au constructeur pour le Nullable<Foo>. De plus, si vous renvoyez un Nullable<Foo>, tout code qui souhaite utiliser la valeur renvoyée doit en faire au moins une copie supplémentaire vers un emplacement de stockage (variable ou temporaire) de type Foo avant de pouvoir faire quoi que ce soit d'utile. En revanche, le code peut utiliser le champ Value d'une variable de type Foo à peu près aussi efficacement que toute autre variable.