Alguém sabe a resposta e / ou tem alguma opinião a respeito?
Como as tuplas normalmente não seriam muito grandes, eu presumiria que faria mais sentido usar structs do que classes para elas. O que diz você?
Alguém sabe a resposta e / ou tem alguma opinião a respeito?
Como as tuplas normalmente não seriam muito grandes, eu presumiria que faria mais sentido usar structs do que classes para elas. O que diz você?
Respostas:
A Microsoft fez com que todos os tipos de tupla referissem tipos no interesse da simplicidade.
Pessoalmente, acho que isso foi um erro. Tuplas com mais de 4 campos são muito incomuns e devem ser substituídas por uma alternativa mais tipificada de qualquer maneira (como um tipo de registro em F #), portanto, apenas pequenas tuplas são de interesse prático. Meus próprios benchmarks mostraram que tuplas não encaixotadas de até 512 bytes ainda podem ser mais rápidas do que tuplas encaixotadas.
Embora a eficiência da memória seja uma preocupação, acredito que o problema dominante é a sobrecarga do coletor de lixo .NET. A alocação e a coleta são muito caras no .NET porque seu coletor de lixo não foi altamente otimizado (por exemplo, em comparação com o JVM). Além disso, o .NET GC (estação de trabalho) padrão ainda não foi paralelizado. Conseqüentemente, programas paralelos que usam tuplas são interrompidos enquanto todos os núcleos disputam o coletor de lixo compartilhado, destruindo a escalabilidade. Essa não é apenas a preocupação dominante, mas a AFAIK foi completamente negligenciada pela Microsoft quando examinou o problema.
Outra preocupação é o envio virtual. Os tipos de referência oferecem suporte a subtipos e, portanto, seus membros são normalmente chamados por meio de envio virtual. Em contraste, os tipos de valor não podem oferecer suporte a subtipos, portanto, a chamada de membro é totalmente inequívoca e pode sempre ser executada como uma chamada de função direta. O envio virtual é extremamente caro no hardware moderno porque a CPU não pode prever onde o contador do programa irá parar. A JVM faz um grande esforço para otimizar o envio virtual, mas o .NET não. No entanto, o .NET oferece uma fuga do envio virtual na forma de tipos de valor. Portanto, representar tuplas como tipos de valor poderia, novamente, ter melhorado dramaticamente o desempenho aqui. Por exemplo, ligandoGetHashCode
em uma tupla de 2, um milhão de vezes leva 0,17s, mas chamá-la em uma estrutura equivalente leva apenas 0,008s, ou seja, o tipo de valor é 20 × mais rápido que o tipo de referência.
Uma situação real em que esses problemas de desempenho com tuplas comumente surgem é no uso de tuplas como chaves em dicionários. Na verdade, tropecei neste tópico seguindo um link da questão Stack Overflow F # executa meu algoritmo mais lento do que Python! onde o programa F # do autor acabou sendo mais lento do que seu Python precisamente porque ele estava usando tuplas encaixotadas. Desembalar manualmente usando um struct
tipo escrito à mão torna seu programa F # várias vezes mais rápido e mais rápido do que Python. Esses problemas nunca teriam surgido se as tuplas fossem representadas por tipos de valor e não por tipos de referência para começar ...
Tuple<_,...,_>
tipos poderiam ter sido lacrados, caso em que nenhum envio virtual seria necessário, apesar de serem tipos de referência. Estou mais curioso para saber por que eles não são selados do que por que são tipos de referência.
A razão é mais provável porque apenas as tuplas menores fariam sentido como tipos de valor, uma vez que teriam uma pequena área de cobertura de memória. As tuplas maiores (ou seja, aquelas com mais propriedades) sofreriam de fato no desempenho, pois seriam maiores que 16 bytes.
Em vez de algumas tuplas serem tipos de valor e outras, tipos de referência e forçar os desenvolvedores a saber quais são quais, imagino que o pessoal da Microsoft pensasse que torná-los todos os tipos de referência era mais simples.
Ah, suspeitas confirmadas! Consulte Construindo Tupla :
A primeira grande decisão foi tratar as tuplas como um tipo de referência ou de valor. Como eles são imutáveis sempre que você quiser alterar os valores de uma tupla, será necessário criar uma nova. Se eles forem tipos de referência, isso significa que pode haver muito lixo gerado se você estiver alterando elementos em uma tupla em um loop fechado. As tuplas F # eram tipos de referência, mas havia um sentimento da equipe de que eles poderiam perceber uma melhoria de desempenho se duas, e talvez três, tuplas de elemento fossem tipos de valor. Algumas equipes que criaram tuplas internas usaram valor em vez de tipos de referência, porque seus cenários eram muito sensíveis à criação de muitos objetos gerenciados. Eles descobriram que usar um tipo de valor lhes proporcionou um melhor desempenho. Em nosso primeiro rascunho da especificação da tupla, mantivemos as tuplas de dois, três e quatro elementos como tipos de valor, com o restante sendo tipos de referência. No entanto, durante uma reunião de design que incluiu representantes de outras línguas, foi decidido que esse design "dividido" seria confuso, devido à semântica ligeiramente diferente entre os dois tipos. A consistência no comportamento e no design foi considerada de maior prioridade do que aumentos de desempenho em potencial. Com base nessa entrada, alteramos o design para que todas as tuplas sejam tipos de referência, embora tenhamos pedido à equipe F # para fazer alguma investigação de desempenho para ver se experimentou um aumento de velocidade ao usar um tipo de valor para alguns tamanhos de tuplas. Teve uma boa maneira de testar isso, já que seu compilador, escrito em F #, foi um bom exemplo de um grande programa que usava tuplas em uma variedade de cenários. No final, a equipe do F # descobriu que não obteve uma melhoria de desempenho quando algumas tuplas eram tipos de valor em vez de tipos de referência. Isso nos fez sentir melhor sobre nossa decisão de usar tipos de referência para tupla.
Se os tipos .NET System.Tuple <...> fossem definidos como estruturas, eles não seriam escalonáveis. Por exemplo, uma tupla ternária de inteiros longos atualmente é dimensionada da seguinte maneira:
type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4
Se a tupla ternária fosse definida como uma estrutura, o resultado seria o seguinte (com base em um exemplo de teste que implementei):
sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104
Como as tuplas têm suporte de sintaxe embutido no F # e são usadas com extrema freqüência nesta linguagem, as tuplas "struct" colocariam os programadores em F # em risco de escrever programas ineficientes sem nem mesmo estarem cientes disso. Isso aconteceria tão facilmente:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
Na minha opinião, tuplas "struct" causariam uma alta probabilidade de criar ineficiências significativas na programação diária. Por outro lado, as tuplas de "classe" existentes atualmente também causam certas ineficiências, conforme mencionado por @Jon. No entanto, acho que o produto da "probabilidade de ocorrência" vezes "dano potencial" seria muito maior com structs do que atualmente com classes. Portanto, a implementação atual é o mal menor.
Idealmente, haveria tuplas de "classe" e tuplas de "estrutura", ambas com suporte sintático em F #!
Editar (07-10-2017)
As tuplas estruturais agora são totalmente suportadas da seguinte maneira:
ref
ou não goste do fato de que as chamadas "structs imutáveis" não combinem, especialmente quando encaixadas. É uma pena. .Net nunca implementou o conceito de passagem de parâmetros por um executável const ref
, já que em muitos casos essa semântica é o que realmente é necessário.
Dictionary
, por exemplo, aqui: stackoverflow.com/questions/5850243 /…
Para 2 tuplas, você ainda pode usar o KeyValuePair <TKey, TValue> de versões anteriores do Common Type System. É um tipo de valor.
Um pequeno esclarecimento para o artigo de Matt Ellis seria que a diferença na semântica de uso entre os tipos de referência e de valor é apenas "leve" quando a imutabilidade está em vigor (o que, é claro, seria o caso aqui). No entanto, acho que teria sido melhor no projeto BCL não introduzir a confusão de ter Tuple passando para um tipo de referência em algum limiar.
Não sei mas se você já usou F # Tuplas fazem parte da linguagem. Se eu fizesse um .dll e retornasse um tipo de Tuplas, seria bom ter um tipo para colocá-lo. Suspeito agora que o F # faz parte da linguagem (.Net 4) algumas modificações no CLR foram feitas para acomodar algumas estruturas comuns em 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 a referência em tipos de tupla C #