¿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?
performance
class
struct
.net-4.0
Bent Rasmussen
fuente
fuente
ValueTuple<...>
. Consulte la referencia en Tipos de tuplas de C #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, llamando
GetHashCode
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 ...fuente
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 :
fuente
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:
fuente
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 ejecutableconst 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.
fuente
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)
fuente