¿Alguien sabe la respuesta y / o tiene una opinión al respecto?
Dado que las tuplas normalmente no serían muy grandes, supongo que tendría más sentido usar estructuras que clases para estas. ¿Lo que usted dice?
¿Alguien sabe la respuesta y / o tiene una opinión al respecto?
Dado que las tuplas normalmente no serían muy grandes, supongo que tendría más sentido usar estructuras que clases para estas. ¿Lo que usted dice?
Respuestas:
Microsoft hizo todos los tipos de tuplas tipos de referencia en aras de la simplicidad.
Personalmente, creo que esto fue un error. Las tuplas con más de 4 campos son muy inusuales y, de todos modos, deben reemplazarse con una alternativa con más tipos (como un tipo de registro en F #), por lo que solo las tuplas pequeñas 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 recolección son muy costosas en .NET porque su recolector de basura no ha sido muy optimizado (por ejemplo, en comparación con la JVM). Además, la estación de trabajo .NET GC (estación de trabajo) predeterminada aún no se ha paralelizado. En consecuencia, los programas paralelos que utilizan 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, sino que, AFAIK, fue completamente ignorada por Microsoft cuando examinaron este problema.
Otra preocupación es el envío virtual. Los tipos de referencia admiten subtipos y, por lo tanto, sus miembros generalmente se invocan a través de un envío virtual. Por el contrario, los tipos de valor no pueden admitir subtipos, por lo que la invocación de miembros es completamente inequívoca y siempre se puede realizar como una llamada de función directa. El envío virtual es enormemente caro en el 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. Sin embargo, .NET proporciona un escape del envío virtual en forma de tipos de valor. Entonces, representar tuplas como tipos de valor podría, nuevamente, haber mejorado dramáticamente el rendimiento aquí. Por ejemplo, llamandoGetHashCode
en una tupla de 2, un millón de veces toma 0,17 s, pero llamarlo en una estructura equivalente toma solo 0,008 s, es decir, el tipo de valor es 20 veces más rápido que el tipo de referencia.
Una situación real en la que surgen comúnmente estos problemas de rendimiento con tuplas es en el uso de tuplas como claves en diccionarios. De hecho, me topé con este hilo siguiendo un enlace de la pregunta de desbordamiento de pila F # ejecuta mi algoritmo más lento que Python. donde el programa F # del autor resultó ser más lento que su Python precisamente porque estaba usando tuplas en caja. El desempaquetado manual con un struct
tipo escrito a mano hace que su programa F # sea varias veces más rápido y más rápido que Python. Estos problemas nunca hubieran surgido si las tuplas estuvieran representadas por tipos de valor y no por tipos de referencia para empezar ...
Tuple<_,...,_>
tipos podrían haberse sellado, en cuyo caso no se necesitaría ningún despacho virtual a pesar de ser tipos de referencia. Tengo más curiosidad por saber por qué no están sellados que por qué son tipos de referencia.
La razón más probable es que solo las tuplas más pequeñas tendrían sentido como tipos de valor, ya que tendrían una pequeña huella de memoria. Las tuplas más grandes (es decir, las que tienen más propiedades) en realidad sufrirían en rendimiento, ya que serían más grandes que 16 bytes.
En lugar de que algunas tuplas sean tipos de valor y otras tipos de referencia y obliguen a los desarrolladores a saber cuáles son cuáles, me imagino que la gente de Microsoft pensó que hacerlos todos los tipos de referencia era más simple.
¡Ah, sospechas confirmadas! Consulte Creación de tupla :
La primera decisión importante fue si tratar las tuplas como referencia o como 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 generarse mucha basura si está cambiando elementos en una tupla en un bucle cerrado. Las tuplas F # eran tipos de referencia, pero el equipo tenía la sensación de que podrían lograr una mejora del rendimiento si dos, y quizás tres, tuplas de elementos fueran tipos de valor. Algunos equipos que habían creado tuplas internas habían utilizado valor en lugar de tipos de referencia, porque sus escenarios eran muy sensibles a la creación de muchos objetos administrados. Descubrieron que el uso de un tipo de valor les daba un mejor rendimiento. En nuestro primer borrador de la especificación de tuplas, 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 coherencia en el comportamiento y el diseño era de mayor prioridad que los posibles aumentos de rendimiento. Basándonos en esta entrada, cambiamos el diseño para que todas las tuplas sean tipos de referencia, aunque le pedimos al equipo de F # que hiciera una investigación de rendimiento para ver si experimentó una aceleración al usar un tipo de valor para algunos tamaños de tuplas. Tenía una buena forma de probar esto, ya que su compilador, escrito en F #, fue un buen ejemplo de un programa grande que usaba tuplas en una variedad de escenarios. Al final, el equipo de F # descubrió que no mejoraba el rendimiento cuando algunas tuplas eran tipos de valor en lugar de tipos de referencia. Esto nos hizo sentir mejor acerca de nuestra decisión de usar tipos de referencia para tuplas.
Si los tipos .NET System.Tuple <...> se definieran como estructuras, no serían escalables. Por ejemplo, una tupla ternaria de enteros largos se escala actualmente 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 definiera 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" pondrían a los programadores de F # en riesgo de escribir programas ineficientes sin siquiera ser conscientes de ello. Sucedería tan fácilmente:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
En mi opinión, las tuplas de "estructura" 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 la "probabilidad de ocurrencia" por el "daño potencial" sería mucho más alto con estructuras de lo que es actualmente con clases. Por lo tanto, la implementación actual es el mal menor.
Idealmente, habría tuplas de "clase" y tuplas de "estructura", ¡ambas con soporte sintáctico en F #!
Editar (2017-10-07)
Las tuplas de estructuras ahora son totalmente compatibles de la siguiente manera:
ref
, o puede que no le guste el hecho de que las llamadas "estructuras inmutables" no lo sean, especialmente cuando están en caja. Es una lástima .net nunca implementó el concepto de pasar parámetros por un ejecutable const ref
, ya que en muchos casos esa semántica es lo que realmente se requiere.
Dictionary
, por ejemplo, aquí: stackoverflow.com/questions/5850243 /…
Para 2 tuplas, aún puede usar KeyValuePair <TKey, TValue> de versiones anteriores del Common Type System. Es un tipo de valor.
Una aclaración menor 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 efecto (que, por supuesto, sería el caso aquí). Sin embargo, creo que habría sido mejor en el diseño de BCL no introducir la confusión de tener Tuple cruzando a un tipo de referencia en algún umbral.
No lo sé, pero si alguna vez has usado F #, las tuplas son parte del lenguaje. Si hice un .dll y devolví un tipo de Tuples, sería bueno tener un tipo para poner eso. Sospecho que ahora 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)
ValueTuple<...>
. Consulte la referencia en Tipos de tuplas de C #