Estou prestes a criar 100.000 objetos em código. São pequenos, com apenas 2 ou 3 propriedades. Vou colocá-los em uma lista genérica e, quando estiverem, vou fazer um loop e verificar o valor a
e talvez atualizar o valor b
.
É mais rápido / melhor criar esses objetos como classe ou estrutura?
EDITAR
uma. As propriedades são tipos de valor (exceto a string, eu acho?)
b. Eles podem (não temos certeza ainda) ter um método de validação
EDITAR 2
Eu estava me perguntando: os objetos no heap e na pilha são processados igualmente pelo coletor de lixo ou isso funciona de forma diferente?
Respostas:
Você é a única pessoa que pode determinar a resposta a essa pergunta. Tente das duas maneiras, meça uma métrica de desempenho significativa, focada no usuário e relevante, e então você saberá se a mudança tem um efeito significativo em usuários reais em cenários relevantes.
As estruturas consomem menos memória heap (porque são menores e mais facilmente compactadas, não porque estão "na pilha"). Mas eles demoram mais para copiar do que uma cópia de referência. Não sei quais são suas métricas de desempenho para uso de memória ou velocidade; há uma troca aqui e você é a pessoa que sabe o que é.
Talvez classe, talvez estrutura. Como regra geral: Se o objeto for:
1. Pequeno
2. Logicamente um valor imutável
3. Há muitos deles,
então eu consideraria torná-lo uma estrutura. Caso contrário, ficaria com um tipo de referência.
Se você precisar alterar algum campo de uma estrutura, geralmente é melhor construir um construtor que retorne uma nova estrutura inteira com o campo definido corretamente. Isso talvez seja um pouco mais lento (meça!), Mas logicamente muito mais fácil de raciocinar.
Não , eles não são iguais porque os objetos na pilha são as raízes da coleção . O coletor de lixo não precisa se perguntar "essa coisa na pilha está viva?" porque a resposta a essa pergunta é sempre "Sim, está na pilha". (Agora, você não pode contar com isso para manter um objeto vivo porque a pilha é um detalhe de implementação. O jitter tem permissão para introduzir otimizações que, digamos, registram o que normalmente seria um valor de pilha, e então nunca está na pilha portanto, o GC não sabe que ainda está vivo. Um objeto registrado pode ter seus descendentes coletados agressivamente, assim que o registro que o mantém não será lido novamente.)
Mas o coletor de lixo não tem que tratar objetos na pilha como vivo, da mesma forma que trata qualquer objeto conhecido por estar vivo como vivo. O objeto na pilha pode se referir a objetos alocados no heap que precisam ser mantidos ativos, portanto, o GC deve tratar os objetos da pilha como objetos alocados no heap vivos para fins de determinar o conjunto ativo. Mas, obviamente, eles não são tratados como "objetos vivos" com o propósito de compactar o heap, porque eles não estão no heap em primeiro lugar.
Está claro?
fonte
readonly
) para permitir otimizações. Eu não deixaria que isso afetasse uma escolha sobre mutabilidade (eu sou louco por detalhes de eficiência na teoria, mas na prática meu primeiro movimento em direção à eficiência é sempre tentar ter uma garantia de correção tão simples quanto possível e, portanto, não preciso desperdiçar ciclos de CPU e ciclos cerebrais em verificações e casos extremos, e ser apropriadamente mutável ou imutável ajuda nisso), mas seria contra qualquer reação instintiva ao fato de você dizer que a imutabilidade pode ser mais lenta.Às vezes,
struct
você não precisa chamar o construtor new () e atribuir diretamente os campos, tornando-o muito mais rápido do que o normal.Exemplo:
é cerca de 2 a 3 vezes mais rápido do que
onde
Value
é umstruct
com dois campos (id
eisValid
).Por outro lado, se os itens precisam ser movidos ou os tipos de valor selecionados, toda essa cópia vai atrasar você. Para obter a resposta exata, suspeito que você precise analisar seu código e testá-lo.
fonte
list
, visto que o código indicado não funcionará com aList<Value>
.As estruturas podem parecer semelhantes às classes, mas existem diferenças importantes das quais você deve estar ciente. Em primeiro lugar, classes são tipos de referência e structs são tipos de valor. Usando structs, você pode criar objetos que se comportam como os tipos integrados e também aproveitar seus benefícios.
Quando você chama o operador New em uma classe, ele é alocado no heap. No entanto, quando você instancia uma estrutura, ela é criada na pilha. Isso produzirá ganhos de desempenho. Além disso, você não lidará com referências a uma instância de uma estrutura como faria com as classes. Você trabalhará diretamente com a instância de struct. Por isso, ao passar uma estrutura para um método, ela é passada por valor em vez de como uma referência.
Mais aqui:
http://msdn.microsoft.com/en-us/library/aa288471(VS.71).aspx
fonte
Arrays de structs são representados no heap em um bloco contíguo de memória, enquanto um array de objetos é representado como um bloco contíguo de referências com os próprios objetos reais em outro lugar no heap, exigindo memória tanto para os objetos quanto para suas referências de array .
Neste caso, como você os está colocando em a
List<>
(e aList<>
está apoiado em um array), seria mais eficiente, em termos de memória, usar structs.(Porém, tenha cuidado, pois grandes arrays encontrarão seu caminho no Large Object Heap onde, se sua vida útil for longa, pode ter um efeito adverso no gerenciamento de memória do seu processo. Lembre-se, também, que a memória não é a única consideração.)
fonte
ref
palavras-chave para lidar com isso.Se eles tiverem semântica de valor, você provavelmente deve usar uma estrutura. Se eles tiverem semântica de referência, você provavelmente deve usar uma classe. Existem exceções, que geralmente tendem a criar uma classe mesmo quando há semântica de valor, mas comece a partir daí.
Quanto à sua segunda edição, o GC lida apenas com o heap, mas há muito mais espaço do heap do que espaço da pilha, portanto, colocar coisas na pilha nem sempre é uma vitória. Além disso, uma lista de tipos de estrutura e uma lista de tipos de classe estarão no heap de qualquer maneira, portanto, isso é irrelevante neste caso.
Editar:
Estou começando a considerar o termo mal prejudicial. Afinal, tornar uma classe mutável é uma má ideia se não for ativamente necessária, e eu não descartaria o uso de uma estrutura mutável. No entanto, é uma ideia ruim com tanta frequência que quase sempre é uma má ideia, mas principalmente ela simplesmente não coincide com a semântica de valor, então simplesmente não faz sentido usar uma estrutura no caso dado.
Pode haver exceções razoáveis com estruturas aninhadas privadas, onde todos os usos dessa estrutura são, portanto, restritos a um escopo muito limitado. Porém, isso não se aplica aqui.
Realmente, eu acho que "muda, então é um mau stuct" não é muito melhor do que continuar sobre o heap e a pilha (o que pelo menos tem algum impacto no desempenho, mesmo que frequentemente deturpado). "Ele sofre mutação, então provavelmente não faz sentido considerá-lo como tendo semântica de valor, então é uma estrutura ruim" é apenas um pouco diferente, mas é importante, eu acho.
fonte
A melhor solução é medir, medir novamente e então medir um pouco mais. Pode haver detalhes do que você está fazendo que podem dificultar uma resposta simplificada e fácil, como "usar estruturas" ou "usar classes".
fonte
Uma estrutura é, em seu cerne, nada mais nada menos do que uma agregação de campos. No .NET é possível que uma estrutura "finja" ser um objeto, e para cada tipo de estrutura o .NET define implicitamente um tipo de objeto heap com os mesmos campos e métodos que - sendo um objeto heap - se comportarão como um objeto . Uma variável que contém uma referência a tal objeto heap (estrutura "em caixa") exibirá semântica de referência, mas aquela que contém uma estrutura diretamente é simplesmente uma agregação de variáveis.
Acho que grande parte da confusão estrutura versus classe vem do fato de que as estruturas têm dois casos de uso muito diferentes, que devem ter diretrizes de design muito diferentes, mas as diretrizes da MS não fazem distinção entre eles. Às vezes, há necessidade de algo que se comporte como um objeto; nesse caso, as diretrizes da MS são bastante razoáveis, embora o "limite de 16 bytes" provavelmente deva ser mais parecido com 24-32. Às vezes, no entanto, o que é necessário é uma agregação de variáveis. Uma estrutura usada para essa finalidade deve consistir simplesmente em um monte de campos públicos e, possivelmente, uma
Equals
substituição, umaToString
substituição eIEquatable(itsType).Equals
implementação. Estruturas que são usadas como agregações de campos não são objetos e não deveriam fingir ser. Do ponto de vista da estrutura, o significado de campo deve ser nada mais nada menos que "a última coisa escrita neste campo". Qualquer significado adicional deve ser determinado pelo código do cliente.Por exemplo, se uma estrutura de agregação de variável tem membros
Minimum
eMaximum
, a própria estrutura não deve prometer issoMinimum <= Maximum
. Código que recebe a estrutura tal como um parâmetro deve se comportar como se fosse aprovada separadaMinimum
eMaximum
valores. Um requisito queMinimum
não seja maior do queMaximum
deve ser considerado como um requisito de que umMinimum
parâmetro não seja maior do que um aprovado separadamenteMaximum
.Um padrão útil a ser considerado às vezes é ter uma
ExposedHolder<T>
classe definida como:Se alguém tem um
List<ExposedHolder<someStruct>>
, ondesomeStruct
é uma estrutura de agregação de variável, pode-se fazer coisas comomyList[3].Value.someField += 7;
, mas fornecermyList[3].Value
a outro código fornecerá o conteúdo de, emValue
vez de fornecer um meio de alterá-lo. Por outro lado, se um usasse umList<someStruct>
, seria necessário usarvar temp=myList[3]; temp.someField += 7; myList[3] = temp;
. Se alguém usasse um tipo de classe mutável, expor o conteúdo domyList[3]
código externo exigiria a cópia de todos os campos para algum outro objeto. Se alguém usasse um tipo de classe imutável ou uma estrutura de "estilo de objeto", seria necessário construir uma nova instância semelhante,myList[3]
exceto pelasomeField
qual fosse diferente, e então armazenar essa nova instância na lista.Uma observação adicional: se você estiver armazenando um grande número de coisas semelhantes, pode ser bom armazená-las em matrizes de estruturas possivelmente aninhadas, de preferência tentando manter o tamanho de cada matriz entre 1K e 64K ou algo assim. Os arranjos de estruturas são especiais, na medida em que uma indexação produzirá uma referência direta a uma estrutura interna, então pode-se dizer "a [12] .x = 5;". Embora seja possível definir objetos semelhantes a matrizes, C # não permite que eles compartilhem essa sintaxe com matrizes.
fonte
Use aulas.
Em uma nota geral. Por que não atualizar o valor b conforme você os cria?
fonte
De uma perspectiva do c ++, concordo que será mais lento modificar as propriedades de uma estrutura em comparação com uma classe. Mas eu realmente acho que eles serão mais rápidos de ler devido à estrutura sendo alocada na pilha em vez de no heap. Ler dados do heap requer mais verificações do que da pilha.
fonte
Bem, se você usar struct afinal, livre-se da string e use char ou buffer de bytes de tamanho fixo.
Isso é re: desempenho.
fonte