Devrais-je utiliser NaN à virgule flottante ou virgule flottante + booléen pour un dataset contenant des valeurs non valides?

J’ai une grande quantité de données à traiter avec des opérations intensives en mathématiques sur chaque dataset. Une grande partie est analogue au traitement d’image. Toutefois, étant donné que ces données sont lues directement à partir d’un périphérique physique, de nombreuses valeurs de pixels peuvent être invalides.

Cela rend la propriété de NaN de représenter des valeurs qui ne sont pas un nombre et de s’étendre sur des opérations arithmétiques très convaincante. Cependant, cela semble également nécessiter la désactivation de certaines optimisations telles que -ffast-math de gcc, et nous devons également être multi-plateformes. Notre conception actuelle utilise une structure simple qui contient une valeur float et une valeur booléenne indiquant la validité.

Bien qu’il semble que NaN ait été conçu avec cette utilisation à l’esprit , d’autres pensent que c’est plus un problème que ça ne vaut . Quelqu’un at-il des conseils basés sur son expérience plus intime de la norme IEEE754 en matière de performances?

BREF: Pour une portabilité maximale, n’utilisez pas de NaN. Utilisez un bit valide séparé. Par exemple, un modèle comme Valid. Toutefois, si vous savez que vous n’utiliserez que des machines IEEE 754-2008, et non pas IEEE 754-1985 (voir ci-dessous), vous pouvez vous en tirer.

Pour des performances optimales, il est probablement plus rapide de ne pas utiliser de NaN sur la plupart des machines auxquelles vous avez access. Toutefois, j’ai participé à la conception matérielle de la PF sur plusieurs machines améliorant les performances de traitement de NaN. Il existe donc une tendance à accélérer les NaN et, en particulier, les NaN de signalisation devraient bientôt être plus rapides que Valid.

DÉTAIL:

Tous les formats à virgule flottante ont des NaN. Tous les systèmes n’utilisent pas la virgule flottante IEEE. La virgule flottante hexadécimale IBM est toujours présente sur certaines machines – en réalité, sur des systèmes, car IBM prend désormais en charge le protocole IEEE FP sur des machines plus récentes.

De plus, IEEE Floating Point avait lui-même des problèmes de compatibilité avec NaN, dans IEEE 754-1985. Voir par exemple wikipedia http://en.wikipedia.org/wiki/NaN :

La norme d’origine IEEE 754 de 1985 (IEEE 754-1985) ne décrivait que les formats binarys à virgule flottante et ne spécifiait pas comment l’état signalé / silencieux devait être balisé. En pratique, le bit le plus significatif de la signification détermine si un NaN est en signalisation ou silencieux. Il en a résulté deux implémentations différentes, avec des significations inversées. * la plupart des processeurs (y compris ceux de la famille Intel / AMD x86-32 / x86-64, de la famille Motorola 68000, de la famille AIM PowerPC, de la famille ARM et de la famille Sun SPARC) règlent le bit signalé / silencieux sur une valeur non nulle si le NaN est calme et à zéro si le NaN est en train de signaler. Ainsi, sur ces processeurs, le bit représente un drapeau ‘is_quiet’. * Dans les NaN générés par les processeurs PA-RISC et MIPS, le bit signalé / calme est à zéro si le NaN est au repos et non nul si le NaN est en signalisation. Ainsi, sur ces processeurs, le bit représente un indicateur “is_signaling”.

Cela, si votre code peut être exécuté sur des machines HP plus anciennes ou sur des machines MIPS actuelles (omniprésentes dans les systèmes embarqués), vous ne devriez pas dépendre d’un codage fixe de NaN, mais d’un #ifdef dépendant de la machine pour vos NaN spéciaux.

La norme IEEE 754-2008 normalise les codages NaN, donc cela s’améliore. Cela dépend de votre marché.

En ce qui concerne les performances: de nombreuses machines interceptent essentiellement, ou asservissent autrement, les performances lors de calculs impliquant à la fois des SNaN (qui doivent piéger) et des QNaN (qui n’ont pas besoin de piéger, c’est-à-dire qui peuvent être rapides – et qui reçoivent plus rapide dans certaines machines que nous parlons.)

Je peux affirmer avec assurance que sur des machines plus anciennes, en particulier des machines Intel plus anciennes, vous ne vouliez PAS utiliser de NaN si vous vous souciez de la performance. Par exemple, http://www.cygnus-software.com/papers/x86andinfinity.html indique “Intel Pentium 4 gère très mal les infinis, les NAN et les dénormaux. … Si vous écrivez du code qui ajoute des nombres à virgule flottante au rythme de Un par cycle d’horloge, puis jetez-y une infinité en entrée, les performances chutent beaucoup, énormément … énormément … les NAN sont encore plus lents. L’addition avec les NAN prend environ 930 cycles … les dénormals sont un peu plus difficiles à mesure.”

Obtenez la photo? Presque 1000 fois plus lent à utiliser un NaN qu’à faire une opération en virgule flottante normale? Dans ce cas, il est presque garanti que l’utilisation d’un modèle comme Valid sera plus rapide.

Cependant, voir la référence à “Pentium 4”? C’est une très vieille page Web. Pendant des années, des gens comme moi ont dit “Les QNaN devraient être plus rapides”, et cela a lentement pris racine.

Plus récemment (2009), Microsoft indique http://connect.microsoft.com/VisualStudio/feedback/details/498934/big-performance-penalty-for-checking-for-nans-or-infinity “Si vous faites des calculs sur des tableaux du double qui contient un grand nombre de NaN ou d’infinités, il y a une pénalité de performances d’un ordre de grandeur. ”

Si je me sens motivé, je peux exécuter une microrégulation sur certaines machines. Mais vous devriez avoir la photo.

Cela devrait changer car il n’est pas si difficile de faire des QNaN rapidement. Mais cela a toujours été un problème d’œuf et de poule: les gars du matériel comme ceux avec qui je travaille disent: “Personne n’utilise NaN, nous ne pourrons donc pas le rendre rapide”, alors que les gars du logiciel n’utilisent pas NaN parce qu’ils sont lents. Pourtant, la marée change lentement.

Heck, si vous utilisez gcc et voulez de meilleures performances, vous activez des optimisations telles que “-ffinite-math-only … … Autorisez les optimisations pour l’arithmétique à virgule flottante qui supposent que les arguments et les résultats ne sont pas des NaNs ou des + -Infs.” La même chose est vraie pour la plupart des compilateurs.

A propos, vous pouvez google comme moi, “NaN performance float” et vérifier vous-même les références. Et / ou lancez vos propres micro-critères.

Enfin, je suppose que vous utilisez un modèle comme

 template class Valid { ... bool valid; T value; ... }; 

J’aime les modèles comme celui-ci, car ils peuvent apporter un “suivi de validité” non seulement à FP, mais également à un entier (valide), etc.

Mais, ils peuvent avoir un gros coût. Les opérations ne sont probablement pas beaucoup plus coûteuses que la manipulation de NaN sur de vieilles machines, mais la densité de données peut être vraiment médiocre. sizeof (valide) peut parfois être 2 * sizeof (float). Cette mauvaise densité peut nuire beaucoup plus aux performances qu’aux opérations impliquées.

En passant, vous devriez envisager une spécialisation des modèles afin que Valid utilise les NaN si elles sont disponibles et rapides, et un bit valide dans le cas contraire.

 template <> class Valid { float value; bool is_valid() { return value != my_special_NaN; } } 

etc.

Quoi qu’il en soit, il vaut mieux avoir le moins de bits valides possible et les ranger ailleurs que de valider à proximité de la valeur. Par exemple

 struct Point { float x, y, z; }; Valid pt; 

est meilleur (en termes de densité) que

 struct Point_with_Valid_Coords { Valid x, y, z; }; 

sauf si vous utilisez des NaN – ou un autre encodage spécial.

Et

 struct Point_with_Valid_Coords { float x, y, z; bool valid_x, valid_y, valid_z }; 

est entre les deux – mais alors vous devez faire tout le code vous-même.

BTW, je suppose que vous utilisez C ++. Si FORTRAN ou Java …

LIGNE INFÉRIEURE: des bits valides séparés sont probablement plus rapides et plus portables.

Mais le traitement de NaN s’accélère et un jour sera suffisant

À propos, ma préférence: créer un modèle valide. Ensuite, vous pouvez l’utiliser pour tous les types de données. Spécialisez-le pour NaNs si cela vous aide. Bien que ma vie accélère les choses, à mon humble avis, il est généralement plus important de nettoyer le code.

Si des données non valides sont très courantes, vous perdez évidemment beaucoup de temps à les exécuter tout au long du traitement. Si les données non valides sont suffisamment communes, il est probablement préférable d’exécuter une sorte de structure de données fragmentée composée uniquement des données valides. Si cela n’est pas très courant, vous pouvez bien sûr conserver une structure de données creuse dont les données ne sont pas valides. De cette façon, vous ne perdriez pas une valeur booléenne pour chaque valeur. Mais peut-être que la mémoire n’est pas un problème pour vous …

Si vous effectuez des opérations telles que la multiplication de deux entrées de données éventuellement non valides, je comprends qu’il est impératif d’utiliser des NaN au lieu d’effectuer des contrôles sur les deux variables pour voir si elles sont valides et pour définir le même indicateur dans le résultat.

A quel sharepointvez-vous être portable? Aurez-vous besoin de pouvoir le porter sur une architecture ne prenant en charge que les points fixes? Si tel est le cas, je pense que votre choix est clair.

Personnellement, je n’utiliserais NaN que s’il s’avérait beaucoup plus rapide. Sinon, je dirais que le code devient plus clair si vous avez un traitement explicite des données non valides.

Comme les nombres à virgule flottante proviennent d’un appareil, leur plage est probablement limitée. Vous pouvez utiliser un autre numéro spécial, plutôt que NaN, pour indiquer l’absence de données, par exemple 1e37. Cette solution est portable. Je ne sais pas si vous êtes plus à l’aise que d’utiliser un drapeau booléen.