performance class struct .net-4.0

performance - ¿Por qué el nuevo tipo de Tuple en.Net 4.0 es un tipo de referencia(clase) y no un tipo de valor(struct)?



class .net-4.0 (5)

¿Alguien sabe la respuesta y / o tiene una opinión al respecto?

Como las tuplas normalmente no serían muy grandes, asumiría que tendría más sentido usar estructuras que clases para ellas. ¿Lo que usted dice?


La razón es más probable porque solo las tuplas más pequeñas tendrían sentido como tipos de valor ya que tendrían una huella de memoria pequeña. Las tuplas más grandes (es decir, las que tienen más propiedades) sufrirían en realidad el rendimiento, ya que serían más grandes que 16 bytes.

En lugar de tener algunas tuplas, ser tipos de valor y otras ser tipos de referencia y obligar a los desarrolladores a saber cuáles son las que yo imaginaba, la gente de Microsoft pensó que hacer todos los tipos de referencia era más simple.

Ah, ¡sospechas confirmadas! Por favor, vea Building Tuple :

La primera decisión importante fue si tratar las tuplas como referencia o tipo de valor. Dado que son inmutables cada vez que desee cambiar los valores de una tupla, debe crear una nueva. Si son tipos de referencia, esto significa que puede haber mucha basura generada si está cambiando elementos en una tupla en un ciclo cerrado. Las tuplas F # fueron tipos de referencia, pero el equipo sintió que podían realizar una mejora en el rendimiento si dos, y quizás tres, tuplas de elementos eran tipos de valores en su lugar. Algunos equipos que habían creado tuplas internas habían usado el valor en lugar de los tipos de referencia, porque sus escenarios eran muy sensibles a la creación de muchos objetos administrados. Descubrieron que usar un tipo de valor les proporcionaba un mejor rendimiento. En nuestro primer borrador de la especificación de tupla, mantuvimos las tuplas de dos, tres y cuatro elementos como tipos de valor, siendo el resto tipos de referencia. Sin embargo, durante una reunión de diseño que incluyó representantes de otros idiomas, se decidió que este diseño "dividido" sería confuso, debido a la semántica ligeramente diferente entre los dos tipos. Se determinó que la consistencia en el comportamiento y el diseño era de mayor prioridad que los posibles aumentos de desempeño. Basándonos en esta información, cambiamos el diseño para que todas las tuplas sean tipos de referencia, aunque le pedimos al equipo F # que realizara alguna investigación de rendimiento para ver si experimentaba una aceleración al usar un tipo de valor para algunos tamaños de tuplas. Tenía una buena manera de probar esto, ya que su compilador, escrito en F #, era un buen ejemplo de un programa grande que usaba tuplas en una variedad de escenarios. Al final, el equipo de F # descubrió que no obtenía una mejora en el rendimiento cuando algunas tuplas eran tipos de valores en lugar de tipos de referencia. Esto nos hizo sentir mejor acerca de nuestra decisión de usar tipos de referencia para tupla.


Microsoft hizo todos los tipos de referencia de tipos de tuplas en aras de la simplicidad.

Personalmente creo que esto fue un error. Las tuplas con más de 4 campos son muy inusuales y deben reemplazarse con una alternativa más tipográfica de todos modos (como un tipo de registro en F #), por lo que solo pequeñas tuplas son de interés práctico. Mis propios puntos de referencia mostraron que las tuplas sin caja de hasta 512 bytes aún podrían ser más rápidas que las tuplas en caja.

Aunque la eficiencia de la memoria es una preocupación, creo que el problema dominante es la sobrecarga del recolector de basura .NET. La asignación y la recopilación son muy caras en .NET porque su recolector de basura no se ha optimizado demasiado (por ejemplo, en comparación con la JVM). Además, el .NET GC (estación de trabajo) predeterminado aún no se ha paralelizado. En consecuencia, los programas paralelos que usan tuplas se detienen cuando todos los núcleos compiten por el recolector de basura compartido, lo que destruye la escalabilidad. Esta no es solo la preocupación dominante, pero AFAIK fue completamente descuidada por Microsoft cuando examinaron este problema.

Otra preocupación es el despacho virtual. Los tipos de referencia admiten subtipos y, por lo tanto, sus miembros suelen invocarse a través del despacho virtual. Por el contrario, los tipos de valores no pueden admitir subtipos, por lo que la invocación de miembros no es ambigua y siempre se puede realizar como una llamada de función directa. El envío virtual es muy costoso en hardware moderno porque la CPU no puede predecir dónde terminará el contador del programa. La JVM hace todo lo posible para optimizar el despacho virtual, pero .NET no lo hace. Sin embargo, .NET proporciona un escape del despacho virtual en forma de tipos de valor. Entonces, representar tuplas como tipos de valores podría, de nuevo, haber mejorado dramáticamente el rendimiento aquí. Por ejemplo, llamar a GetHashCode en un 2-tuple un millón de veces toma 0.17s pero llamarlo a una estructura equivalente toma solo 0.008s, es decir, el tipo de valor es 20 veces más rápido que el tipo de referencia.

Una situación real donde comúnmente surgen estos problemas de rendimiento con tuplas es en el uso de tuplas como claves en los diccionarios. De hecho, me encontré con este hilo siguiendo un enlace de la pregunta de Desbordamiento de pila . F # ejecuta mi algoritmo más despacio que Python. donde el programa F # del autor resultó ser más lento que su Python precisamente porque estaba usando tuplas en caja. Desbloquear manualmente el uso de un tipo de struct manuscrita hace que su programa F # sea varias veces más rápido y más rápido que Python. Estos problemas nunca habrían surgido si las tuplas estuvieran representadas por tipos de valores y no por tipos de referencia, para empezar ...


No sé, pero si alguna vez has usado F # Tuples son parte del lenguaje. Si hice un .dll y devolví un tipo de Tuples, sería bueno tener un tipo para ponerlo. Sospecho que ahora que F # es parte del lenguaje (.Net 4) se hicieron algunas modificaciones a CLR para acomodar algunas estructuras comunes en F #

De http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Records

let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);; val scalarMultiply : float -> float * float * float -> float * float * float scalarMultiply 5.0 (6.0, 10.0, 20.0);; val it : float * float * float = (30.0, 50.0, 100.0)


Para 2-tuplas, aún puede usar KeyValuePair <TKey, TValue> de versiones anteriores del Common Type System. Es un tipo de valor.

Una pequeña aclaración al artículo de Matt Ellis sería que la diferencia en la semántica de uso entre los tipos de referencia y de valor es solo "leve" cuando la inmutabilidad está en vigencia (lo cual, por supuesto, sería el caso aquí). Sin embargo, creo que hubiera sido mejor en el diseño de BCL no introducir la confusión de hacer que Tuple se cruzara con un tipo de referencia en algún umbral.


Si los tipos .NET System.Tuple<...> se definieron como estructuras, no serían escalables. Por ejemplo, una tupla ternaria de enteros largos actualmente se escala de la siguiente manera:

type Tuple3 = System.Tuple<int64, int64, int64> type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3> sizeof<Tuple3> // Gets 4 sizeof<Tuple33> // Gets 4

Si la tupla ternaria se definió como una estructura, el resultado sería el siguiente (basado en un ejemplo de prueba que implementé):

sizeof<Tuple3> // Would get 32 sizeof<Tuple33> // Would get 104

Como las tuplas tienen soporte de sintaxis incorporado en F #, y se usan con mucha frecuencia en este lenguaje, las tuplas "struct" presentarían a los programadores F # en riesgo de escribir programas ineficientes sin siquiera ser conscientes de ello. Pasaría tan fácilmente:

let t3 = 1L, 2L, 3L let t33 = t3, t3, t3

En mi opinión, las tuplas "struct" causarían una alta probabilidad de crear ineficiencias significativas en la programación diaria. Por otro lado, las tuplas de "clase" actualmente existentes también causan ciertas ineficiencias, como lo menciona @Jon. Sin embargo, creo que el producto de "probabilidad de ocurrencia" multiplicado por "daño potencial" sería mucho más alto con las estructuras de lo que es actualmente con las clases. Por lo tanto, la implementación actual es el mal menor.

Idealmente, ¡habría tanto tuplas de "clase" como de "struct", ambas con soporte sintáctico en F #!

Editar (2017-10-07)

Las tuplas de estructura ahora son totalmente compatibles de la siguiente manera: