2009-05-18 21 views
12

Estoy escribiendo algoritmos que funcionan en series de datos numéricos, donde a veces, un valor en la serie debe ser nulo. Sin embargo, debido a que esta aplicación es crítica para el rendimiento, he evitado el uso de tipos anulables. He probado los algoritmos para comparar específicamente el rendimiento del uso de tipos anulables vs tipos no anulables, y en el mejor de los casos, los tipos anulables son 2 veces más lentos, pero a menudo mucho peores.Alternativas a tipos anulables en C#

El tipo de datos más utilizado es el doble, y actualmente la alternativa elegida para nulo es double.NaN. Sin embargo, entiendo que este no es el uso previsto exacto para el valor de NaN, por lo que no estoy seguro de si hay algún problema con esto que no pueda prever y cuál sería la mejor práctica.

Estoy interesado en saber cuáles son las mejores alternativas nulos son para los siguientes tipos de datos, en particular: doble/flotador, decimal, fecha y hora, int/largas (aunque otros son más que bienvenidos)

Edit: Creo que necesito aclarar mis requisitos sobre el rendimiento. Gigs de datos numéricos se procesan a través de estos algoritmos en un momento que toma varias horas. Por lo tanto, aunque la diferencia entre, por ejemplo, 10ms o 20ms suele ser insignificante, en este escenario realmente tiene un impacto significativo en el tiempo empleado.

+0

Alguna información sobre el tipo y la cantidad de datos que espera sería buena. – peterchen

+1

Para más información, mis pruebas muestran que, para el caso en que ninguno de los valores es nulo, los números nulos y mágico son bastante iguales; cuando se trata de un nulo, sí, el enfoque del número mágico es un poco más rápido ... ¿pero es lo suficientemente rápido como para valer la molestia? Todavía es muy, muy rápido - 50M iteraciones en 86ms (número mágico) vs 144ms (Nullable ) en mi máquina ... –

+0

(vea también mi respuesta a su prueba i == null etc; el compilador ya lo hace a través de " levantado "operadores, es probable que lo duplique ...) –

Respuesta

18

Bueno, si ha descartado Nullable<T>, le quedan valores de dominio, es decir, un número mágico que considera nulo. Si bien esto no es ideal, tampoco es poco común; por ejemplo, gran parte del código del marco principal trata a DateTime.MinValue de la misma manera que nulo. Esto, al menos, mueve el daño lejos de los valores comunes ...

edición para resaltar únicamente cuando no haya NaN

Entonces, ¿dónde no hay NaN, tal vez usar .MinValue - pero recuerda lo que sucede si usted males accidentalmente use ese mismo valor significa el mismo número ...

Obviamente para datos sin firmar necesitará .MaxValue (¡evite cero!).

Personalmente, trataría de usar Nullable<T> como expresión de mi intención de forma más segura ... puede haber formas de optimizar su código Nullable<T>, tal vez. Y también: para cuando hayas comprobado el número mágico en todos los lugares que necesitas, ¿no será mucho más rápido que Nullable<T>?

+0

Acepto, creo que esta es una alternativa mucho mejor que los dobles, a menos que deba tener long.MaxValue sea válido. – BobbyShaftoe

+1

Para valores dobles o flotantes, NaN o uno de los infinitos se pueden usar como valor "nulo", si no los necesita. – Joey

+0

Con respecto a los cheques, los tipos nulos requieren el mismo número de cheques, donde verifico si hay un número mágico, verifico nulo. Entonces las pruebas de perfusión que realicé sí lo tuvieron en cuenta. Estoy de acuerdo que no es ideal, pero en este escenario, el rendimiento es no. 1 prioridad Y en este escenario, ¿la diferencia de rendimiento entre operaciones tan simple como int + int e int? + int? es significante. – Ryan

4

No estoy de acuerdo con Gravell en este caso específico: una variable nula se considera "no definida", no tiene ningún valor. Entonces, cualquier cosa que se use para indicar que está bien: incluso números mágicos, pero con números mágicos debes tener en cuenta que un número mágico siempre te atormentará en el futuro cuando se vuelva un valor 'válido' de repente. Con Double.NaN no tienes que temer por eso: nunca va a convertirse en un doble válido. Sin embargo, debes tener en cuenta que NaN, en el sentido de la secuencia de dobles, solo se puede usar como marcador para "no definido", obviamente no puedes usarlo como un código de error en las secuencias.

Así que cualquier cosa que se use para marcar 'indefinido': tiene que quedar claro en el contexto del conjunto de valores que ese valor específico se considera el valor para 'indefinido' Y que no cambiará en el futuro.

Si Nullable te da demasiados problemas, usa NaN, o lo que sea, siempre que consideres las consecuencias: el valor elegido representa 'indefinido' y se mantendrá.

+0

Tienes razón, y no estaba claro. Solo me refería al MinValue, etc. para esos momentos en los que no hay NaN - int, largo, decimal, DateTime, etc. Para double/float, NaN es la respuesta obvia (que yo había supuesto, a partir de la pregunta). –

2

respuesta parcial:

Float y Double proporcionan NaN (Not a Number). NaN es un poco complicado ya que, por especificación, NaN! = NaN. Si quiere saber si un número es NaN, deberá usar Double.IsNaN().

Ver también Binary floating point and .NET.

+1

Como un aparte ... en la mayoría de las bases de datos, null! = Null también, por lo que este no es necesariamente un territorio inesperado ... pero sí: es diferente a cómo C# maneja la igualdad de Nullable . –

4

estoy trabajando en un gran proyecto que utiliza NaN como un valor null. No me siento completamente cómodo con eso, por razones similares a las tuyas: no saber qué puede salir mal. No hemos encontrado ningún problema real hasta ahora, pero estar al tanto de los siguientes:

aritmética NaN - Si bien, la mayoría de las veces, "NaN promoción" es una buena cosa, puede que no sea siempre lo que se esperar.

Comparación - La comparación de valores se vuelve bastante costosa, si quiere que los NaNs se igualen. Ahora, probar flotadores para la igualdad no es simple de todos modos, pero ordenar (un < b) puede ponerse realmente feo, porque a veces los nan necesitan ser más pequeños, a veces más grandes que los valores normales.

Código de infección - Veo muchos códigos aritméticos que requieren un manejo específico de NaN para ser correctos. Así que terminas con "funciones que aceptan NaN" y "funciones que no" por razones de rendimiento.

Otros no finitos NaN es el único valor no finito. Debe tenerse en cuenta ...

Las excepciones de punto flotante no son un problema cuando están desactivadas. Hasta que alguien los habilite. Verdadera historia: Inicialización estática de un NaN en un control ActiveX. No suena aterrador, hasta que cambie la instalación para usar InnoSetup, que usa un núcleo Pascal/Delphi (?), Que tiene excepciones FPU habilitadas por defecto. Me tomó un tiempo averiguarlo.

Así que, en definitiva, nada grave, aunque preferiría no tener que tener en cuenta que a menudo NaNs.


que haría uso de tipos anulables tan a menudo como sea posible, a menos que sean (demostrado ser) las limitaciones de rendimiento/ressource. Un caso podría ser grandes vectores/matrices con NaNs ocasionales, o grandes conjuntos de valores individuales nombrados donde el comportamiento de NaN predeterminado es correcto.


Alternativamente, se puede usar un vector de índice de vectores y matrices, implementaciones estándar "matriz dispersa", o un vector bool/bit separado.

0

Tal vez la disminución significativa del rendimiento sucede cuando se llama a uno de los miembros o propiedades de anulables (boxeo).

intenta utilizar una estructura con la doble + un booleano indica si se especifica o no el valor.

+0

Pero los tipos anulables ya son estructuras ... – Ryan

+0

Eso es exactamente lo que permite Nullable - es una estructura, tiene un valor (por ejemplo, de tipo doble) y un booleano que indica que tiene o no tiene valor asignado. Y sin boxeo por encima. –

+0

Las propiedades (HasValue y Value) son métodos internos (get_HasValue y get_Value). Por lo tanto, están sujetos al boxeo (a condición de que no aparezca ningún compilador especial de magia para Nullable aquí). –

0

Uno puede evitar algunos de la degradación del rendimiento asociada con Nullable<T> mediante la definición de su propia estructura de

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

Si se desea, se puede definir constructor, o un operador de conversión de T a MaybeValid<T>, etc., pero el uso excesivo de tales las cosas pueden producir un rendimiento subóptimo. Las estructuras de campo expuesto pueden ser eficientes si se evita la copia innecesaria de datos. Algunas personas pueden fruncir el ceño ante la noción de campos expuestos, pero pueden ser masivamente más eficientes que las propiedades. Si una función que devolverá un T necesitaría tener una variable de tipo T para mantener su valor de retorno, el uso de un MaybeValid<Foo> simplemente aumenta en 4 el tamaño de la cosa que se va a devolver. Por el contrario, usar un Nullable<Foo> requeriría que la función primero calcule el Foo y luego pase una copia al constructor para el Nullable<Foo>. Además, devolver un Nullable<Foo> requerirá que cualquier código que desee utilizar el valor devuelto debe hacer al menos una copia adicional en una ubicación de almacenamiento (variable o temporal) del tipo Foo antes de que pueda hacer algo útil con él. Por el contrario, el código puede usar el campo Value de una variable del tipo Foo con la misma eficacia que cualquier otra variable.