Boxing e Unboxing (com exemplos em C#)

Nesse terceiro post sobre conceitos fundamentais para performance, iremos tratar de Boxing e Unboxing.

Há outros dois posts, dessa série, um falando sobre a stack e outro sobre a heap, que mostram conceitos fundamentais para que você possa entender do que estaremos tratando aqui. Caso ainda não lido esses posts, ou ainda não esteja seguro quanto a esses conceitos, recomendo tomar um tempo para a leitura e, eventualmente, resolver suas dúvidas nos comentários.

O que é Boxing e Unboxing

Boxing ocorre quando um valor (literal ou struct) está na stack e, por alguma razão, o movemos para a heap. Unboxing é o processo inverso.

Considere o seguinte exemplo:

class Program 
{
  public static void Main()
  {
    int value_stack = 4;
    object value_heap = value_stack; // boxing
    int value_unboxed = (int) value_heap;
  }
}

No código acima, value_stack é uma variável de um tipo primitivo (que é alocado na stack). Quando atribuímos o valor dessa variável para outra, do tipo object, forçamos que o valor seja levado para a heap (acontecendo o boxing) afinal, todas os valores do tipo object e seus derivados (todas as classes que você escreve) só podem ser armazenados na heap. Por fim, quando criamos uma variável de um tipo primitivo e fazemos o cast de um object para este tipo, trazemos o valor de volta para stack (acontecendo o unboxing)

Por que boxing e unboxing é importante para performance?

Há alguns aspectos fundamentais aqui:

  1. valores na heap ocupam mais memória – cada variável que que aponta para valores na heap tem um ponteiro (de 32 bits ou 64 bits) com a posição de memória desses valores (espaço adicional ao espaço ocupado pelo objeto em si). Além disso, os objetos na heap estão sempre envoltos em uma estrutura de dados.
  2. acesso a heap é mais lento, por razões óbvias
  3. a desalocação de objetos na heap é mais custosa – em .NET, a desalocação de objetos da heap ocorre de forma não determinística através da execução do Garbage Collector que, por sua vez, suspende a execução da aplicação sempre que é executado.

Fazer boxing de forma descuidada pode influenciar consideravelmente o tempo de execução da aplicação e o volume de memória RAM consumido.

O problema do boxing não evidente

Considere o código que segue:

using System;
using System.Diagnostics;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var f1 = new Foo {Value = 5};
            var f2 = new Foo {Value = 5};
            var f3 = new Foo {Value = 6};

            var sw = new Stopwatch();
            sw.Start();
            var gc0 = GC.CollectionCount(0);
            var gc1 = GC.CollectionCount(1);

            for (int i = 0; i < 10_000_000; i++)
            {
                if (!f1.Equals(f2))
                {
                   Console.WriteLine("Failed");
                }

                if (f1.Equals(f3))
                {
                    Console.WriteLine("Failed");
                }
            }

            Console.WriteLine($"Ellapsed time: {sw.ElapsedMilliseconds} ms");
            Console.WriteLine($"GC0 count    : {GC.CollectionCount(0) - gc0 }");
            Console.ReadLine();
        }
    }

    struct Foo
    {
        public int Value { get; set; }
    }
}

A execução desse código, em meu computador, gerou a seguinte saída:

Como você pode ver, foram 228 coletas (intervenções do garbage collector, com suspensão, em menos de um segundo de execução.) em menos de um segundo.

Seguramente, a implementação padrão de Equals é bem pouco eficiente (gerando uma quantidade de alocações alta, principalmente por fazer uso intensivo de reflection). Mas, uma implementação personalizada não resolve inteiramente o problema.

struct Foo
{
    public int Value { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        if (obj is Foo f)
        {
            return f.Value == this.Value;
        }

        return false;
    }
}

Nessa implementação, fazemos uma comparação econômica entre as instâncias.

O volume de acionamentos do Garbage Collector caiu consideravelmente. Entretanto, ainda ocorre.

O problema, é que o parâmetro de Equals é do tipo object. Assim, a cada chamada, temos um boxing levando a struct para a heap.

Vamos revisitar esse problema, implementando a interface de comparação apropriada que nos permite impedir o boxing.

struct Foo : IEquatable
{
    public int Value { get; set; }


    public bool Equals(Foo other)
    {
        return Value == other.Value;
    }
}

Como você pode ver, dessa vez, o parâmetro para Equals é o tipo da struct. Ou seja, não ocorrerá boxing.

Executando o código, temos o seguinte resultado:

Sem boxing, sem garbage collections. Sem garbage collections, sem interrupções. Sem interrupções, o programa roda muito mais rápido.

Há, em toda API do .NET centenas de métodos esperando argumentos object. Sempre que passamos uma struct ou um tipo primitivo como parâmetro, o boxing acontece.

Há ainda outros exemplos. Mas que não vamos tratar aqui hoje.

Fechando … por enquanto

Neste post, conseguimos demonstrar o que é boxing (e unboxing) destacando o impacto para a performance.

Nos próximos posts dessa série, continuaremos tratando das pequenas armadilhas técnicas que, sem que percebamos, consomem importantes recursos computacionais.

Mais uma vez, recomendamos que você veja o post que explica a stack e outro sobre a heap. Outra boa pedida é ver como Rust gerencia a heap (afinal, há vida fora de .NET). Por fim, se estiver trabalhando com algum problema que exija processamento paralelo massivo, sugerimos que considere CUDA.

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
4 Comentários
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Carlos Eduardo
Carlos Eduardo
4 anos atrás

Parabéns pelo post Elemar, de fato este tipo de conteúdo agrega muito mais que saber organizar pastas.

Raphael Cardoso
Raphael Cardoso
4 anos atrás

Parabéns Elemar!
Achei excelente os artigos sobre esses fundamentos. Espero ver mais sobre esse assunto.
Desenvolvo C# a anos, sempre me preocupei com performe do que desenvolvo, mas nunca havia feito uma analise a esse nível.
Apoio essa sua idéia de que temos que conhecer bem o fundamento.

Abraço e sucesso!

Vinicius Mamoré
Vinicius Mamoré
4 anos atrás

Fantástico como sempre, valeu Elemar!

Edivan Camargo
Edivan Camargo
4 anos atrás

Fantástico!

AUTOR

Elemar Júnior
Fundador e CEO da EximiaCo atua como tech trusted advisor ajudando empresas e profissionais a gerar mais resultados através da tecnologia.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Arquivo

Pós-pandemia, trabalho remoto e a retenção dos profissionais de TI

CTO Consulting e Especialista em Execução em TI
EximiaCo 2024 - Todos os direitos reservados
4
0
Queremos saber a sua opinião, deixe seu comentáriox
()
x
Oferta de pré-venda!

Mentoria em
Arquitetura de Software

Práticas, padrões & técnicas para Arquitetura de Software, de maneira efetiva, com base em cenários reais para profissionais envolvidos no projeto e implantação de software.

Muito obrigado!

Deu tudo certo com seu envio!
Logo entraremos em contato

Boxing e Unboxing (com exemplos em C#)

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Boxing e Unboxing (com exemplos em C#)

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?