Quanto é a sobrecarga de pointers inteligentes em comparação com pointers normais em C ++?

Quanto é a sobrecarga de pointers inteligentes em comparação com pointers normais no C ++ 11? Em outras palavras, meu código será mais lento se eu usar pointers inteligentes e, em caso afirmativo, quanto mais lento?

Especificamente, estou perguntando sobre o C ++ 11 std::shared_ptr e std::unique_ptr .

Obviamente, o material empurrado para baixo da pilha vai ser maior (pelo menos eu acho que sim), porque um ponteiro inteligente também precisa armazenar seu estado interno (contagem de referência, etc), a questão é, quanto isso vai afetar meu desempenho, se em tudo?

Por exemplo, eu retorno um ponteiro inteligente de uma function em vez de um ponteiro normal:

 std::shared_ptr getValue(); // versus const Value *getValue(); 

Ou, por exemplo, quando uma das minhas funções aceita um ponteiro inteligente como parâmetro em vez de um ponteiro normal:

 void setValue(std::shared_ptr val); // versus void setValue(const Value *val); 

std::unique_ptr tem sobrecarga de memory apenas se você fornecer algum deleter não-trivial.

std::shared_ptr sempre tem overhead de memory para o contador de referência, embora seja muito pequeno.

std::unique_ptr tem overhead de tempo apenas durante o construtor (se tiver que copiar o deleter fornecido e / ou inicializar nulo o ponteiro) e durante o processo de destruição (para destruir o object de propriedade).

std::shared_ptr tem sobrecarga de tempo no construtor (para criar o contador de referência), no destruidor (para diminuir o contador de referência e, possivelmente, destruir o object) e no operador de atribuição (para incrementar o contador de referência). Devido a garantias de segurança de thread de std::shared_ptr , esses incrementos / decrementos são atômicos, adicionando assim um pouco mais de sobrecarga.

Observe que nenhum deles tem sobrecarga de tempo na desreferência (ao obter a referência ao object de propriedade), enquanto essa operação parece ser a mais comum para os pointers.

Em suma, existe alguma sobrecarga, mas não deve tornar o código lento, a menos que você crie e destrua continuamente os pointers inteligentes.

Como acontece com todo o desempenho do código, o único meio realmente confiável de obter informações concretas é medir e / ou inspecionar o código da máquina.

Dito isso, o raciocínio simples diz que

  • Você pode esperar alguma sobrecarga nas compilações de debugging, pois, por exemplo, operator-> deve ser executado como uma chamada de function para que você possa entrar nela (isto é devido à falta geral de suporte para marcação de classs e funções como não-debug).

  • Para shared_ptr você pode esperar alguma sobrecarga na criação inicial, desde que envolva alocação dinâmica de um bloco de controle, e alocação dinâmica é muito mais lenta que qualquer outra operação básica em C ++ (use make_shared quando praticamente possível, para minimizar essa sobrecarga).

  • Também para shared_ptr há alguma sobrecarga mínima na manutenção de uma contagem de referência, por exemplo, ao passar um shared_ptr por valor, mas não existe essa sobrecarga para unique_ptr .

Mantendo o primeiro ponto acima em mente, quando você mede, faça isso para as versões de debugging e liberação.

O comitê internacional de padronização C ++ publicou um relatório técnico sobre desempenho , mas isso foi em 2006, antes que unique_ptr e shared_ptr fossem adicionados à biblioteca padrão. Ainda assim, os pointers inteligentes eram antigos, então o relatório também considerou isso. Citando a parte relevante:

“Se o access a um valor por meio de um ponteiro inteligente trivial for significativamente mais lento do que acessá-lo por meio de um ponteiro comum, o compilador está manipulando ineficientemente a abstração. No passado, a maioria dos compiladores tinha penalidades de abstração significativas e vários compiladores atuais ainda o fazem. No entanto, pelo menos dois compiladores foram relatados para ter penas de abstração abaixo de 1% e outro uma penalidade de 3%, eliminando assim este tipo de sobrecarga está bem dentro do estado da arte ”

Como um palpite informado, o “bem dentro do estado da arte” foi alcançado com os compiladores mais populares hoje, a partir do início de 2014.

Minha resposta é diferente dos outros e eu realmente me pergunto se eles já perfilou o código.

shared_ptr tem uma sobrecarga significativa para criação devido à sua alocação de memory para o bloco de controle (que mantém o contador de referência e uma lista de pointers para todas as referências fracas). Ele também tem uma enorme sobrecarga de memory devido a isso e ao fato de que std :: shared_ptr é sempre uma tupla de 2 pointers (um para o object, um para o bloco de controle).

Se você passar um shared_pointer para uma function como um parâmetro de valor, ele será pelo menos 10 vezes mais lento que uma chamada normal e criará muitos códigos no segmento de código para o desenrolar da pilha. Se você passá-lo por referência, você recebe uma indirecção adicional, que também pode ser muito pior em termos de desempenho.

É por isso que você não deve fazer isso a menos que a function esteja realmente envolvida no gerenciamento de propriedade. Caso contrário, use “shared_ptr.get ()”. Ele não foi projetado para garantir que seu object não seja eliminado durante uma chamada de function normal.

Se você enlouquecer e usar shared_ptr em pequenos objects como uma tree de syntax abstrata em um compilador ou em pequenos nós em qualquer outra estrutura de gráfico, você verá uma enorme queda de desempenho e um enorme aumento de memory. Eu vi um sistema parser que foi reescrito logo após o C ++ 14 chegar ao mercado e antes do programador aprender a usar corretamente os pointers inteligentes. A reescrita foi uma magnitude mais lenta que o código antigo.

Não é uma bala de prata e pointers brutos também não são ruins por definição. Maus programadores são maus e o mau design é ruim. Projete com cuidado, projete com propriedade clara em mente e tente usar o shared_ptr principalmente no limite da API do subsistema.

Se você quiser saber mais, você pode assistir Nicolai M. Josuttis boa conversa sobre “O preço real dos pointers compartilhados em C ++” https://vimeo.com/131189627
Ele vai fundo nos detalhes de implementação e na arquitetura da CPU para barreiras de gravação, bloqueios atômicos, etc. uma vez ouvindo, você nunca vai falar sobre esse recurso ser barato. Se você quiser apenas uma prova da magnitude mais lenta, ignore os primeiros 48 minutos e observe-o executando um código de exemplo que é executado até 180 vezes mais lento (compilado com -O3) ao usar o ponteiro compartilhado em todos os lugares.

Em outras palavras, meu código será mais lento se eu usar pointers inteligentes e, em caso afirmativo, quanto mais lento?

Mais devagar? O mais provável é que não, a menos que você esteja criando um índice enorme usando shared_ptrs e não tenha memory suficiente a ponto de seu computador começar a se enrugar, como uma velha sendo jogada no chão por uma força insuportável vinda de longe.

O que tornaria seu código mais lento é buscas lentas, processamento de loop desnecessário, cópias enormes de dados e muitas operações de gravação em disco (como centenas).

As vantagens de um ponteiro inteligente estão todas relacionadas ao gerenciamento. Mas a sobrecarga é necessária? Isso depende da sua implementação. Vamos dizer que você está interagindo com uma matriz de 3 fases, cada fase tem uma matriz de 1024 elementos. Criar um smart_ptr para esse processo pode ser um exagero, pois uma vez que a iteração é feita, você saberá que precisa apagá-la. Então você pode ganhar memory extra por não usar um smart_ptr

Mas você realmente quer fazer isso?

Um único memory leaks pode fazer com que seu produto tenha um ponto de falha no tempo (digamos que seu programa vaze 4 megabytes por hora, levaria meses para quebrar um computador, mas ele quebrará, você sabe porque o vazamento está lá) .

É como dizer “seu software é garantido por 3 meses, então, me ligue para o serviço”.

Então, no final, é realmente uma questão de … você pode lidar com esse risco? O uso de um ponteiro bruto para manipular sua indexação em centenas de objects diferentes vale a pena perder o controle da memory.

Se a resposta for sim, use um ponteiro bruto.

Se você nem quer pensar nisso, um smart_ptr é uma solução boa, viável e incrível.