Qual é a sobrecarga de ponteiros inteligentes em comparação com ponteiros normais em C ++ 11? Em outras palavras, meu código ficará mais lento se eu usar ponteiros inteligentes e, em caso afirmativo, quanto mais lento?
Especificamente, estou perguntando sobre o C ++ 11 std::shared_ptr
e std::unique_ptr
.
Obviamente, as coisas empurradas para baixo na pilha serão maiores (pelo menos eu acho), porque um ponteiro inteligente também precisa armazenar seu estado interno (contagem de referência, etc), a questão realmente é, quanto isso vai afetam meu desempenho, se afetam?
Por exemplo, eu retorno um ponteiro inteligente de uma função em vez de um ponteiro normal:
std::shared_ptr<const Value> 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<const Value> val);
// versus
void setValue(const Value *val);
fonte
std::unique_ptr
oustd::shared_ptr
?Respostas:
std::unique_ptr
tem sobrecarga de memória apenas se você fornecê-lo com algum deletador não trivial.std::shared_ptr
sempre tem sobrecarga de memória para o contador de referência, embora seja muito pequeno.std::unique_ptr
tem sobrecarga de tempo apenas durante o construtor (se ele tiver que copiar o deleter fornecido e / ou inicializar nulo o ponteiro) e durante o destruidor (para destruir o objeto 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 objeto) e no operador de atribuição (para incrementar o contador de referência). Devido às garantias de segurança de threadstd::shared_ptr
, esses incrementos / decréscimos são atômicos, adicionando mais sobrecarga.Observe que nenhum deles tem uma sobrecarga de tempo na desreferenciação (ao obter a referência ao objeto possuído), enquanto esta operação parece ser a mais comum para ponteiros.
Para resumir, há alguma sobrecarga, mas não deve tornar o código lento, a menos que você crie e destrua ponteiros inteligentes continuamente.
fonte
unique_ptr
não tem sobrecarga no destruidor. Ele faz exatamente o mesmo que faria com um ponteiro bruto.std::unique_ptr
? Se você construir umstd::unique_ptr<int>
, o internoint*
será inicializado,nullptr
quer você goste ou não.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 de máquina.
Dito isso, o raciocínio simples diz que
Você pode esperar alguma sobrecarga em compilações de depuração, uma vez que, por exemplo,
operator->
deve ser executado como uma chamada de função para que você possa entrar nela (isso, por sua vez, é devido à falta geral de suporte para marcar classes e funções como não depuradas).Pois
shared_ptr
você pode esperar alguma sobrecarga na criação inicial, uma vez que envolve a alocação dinâmica de um bloco de controle, e a alocação dinâmica é muito mais lenta do que qualquer outra operação básica em C ++ (usemake_shared
quando for praticamente possível, para minimizar essa sobrecarga).Além disso,
shared_ptr
há alguma sobrecarga mínima na manutenção de uma contagem de referência, por exemplo, ao passar umshared_ptr
valor por, mas não há sobrecarga paraunique_ptr
.Mantendo o primeiro ponto acima em mente, ao medir, faça isso tanto para compilações de depuração quanto de liberação.
O comitê internacional de padronização C ++ publicou um relatório técnico sobre o desempenho , mas isso foi em 2006, antes
unique_ptr
eshared_ptr
foi adicionado à biblioteca padrão. Ainda assim, as dicas inteligentes eram ultrapassadas naquele ponto, então o relatório também considerou isso. Citando a parte relevante:Como um palpite bem informado, o “bem dentro do estado da arte” foi alcançado com os compiladores mais populares de hoje, desde o início de 2014.
fonte
Minha resposta é diferente das outras e realmente me pergunto se eles já criaram um perfil de código.
shared_ptr tem uma sobrecarga significativa para a criação por causa de sua alocação de memória para o bloco de controle (que mantém o contador ref e uma lista de ponteiros para todas as referências fracas). Ele também tem uma grande sobrecarga de memória por causa disso e do fato de que std :: shared_ptr é sempre uma tupla de 2 ponteiros (um para o objeto, um para o bloco de controle).
Se você passar um ponteiro_compartilhado para uma função 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ê passar por referência, terá uma indireção adicional que também pode ser bem pior em termos de desempenho.
É por isso que você não deve fazer isso, a menos que a função esteja realmente envolvida no gerenciamento de propriedade. Caso contrário, use "shared_ptr.get ()". Ele não foi projetado para garantir que seu objeto não seja morto durante uma chamada de função normal.
Se você enlouquecer e usar shared_ptr em pequenos objetos como uma árvore de sintaxe abstrata em um compilador ou em pequenos nós em qualquer outra estrutura de gráfico, você verá uma grande queda no desempenho e um grande aumento na memória. Eu vi um sistema analisador que foi reescrito logo após o C ++ 14 chegar ao mercado e antes que o programador aprendesse a usar ponteiros inteligentes corretamente. A reescrita foi uma magnitude mais lenta do que o código antigo.
Não é uma bala de prata e os indicadores brutos também não são ruins por definição. Programadores ruins são ruins e design ruim é 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, pode assistir a uma boa palestra de Nicolai M. Josuttis sobre "O preço real dos ponteiros compartilhados em C ++" https://vimeo.com/131189627
Ele se aprofunda nos detalhes de implementação e arquitetura de CPU para barreiras de gravação, atômica depois de ouvir, você nunca mais vai falar sobre esse recurso ser barato. Se você quiser apenas uma prova da magnitude mais lenta, pule os primeiros 48 minutos e observe-o executando o código de exemplo que é executado até 180 vezes mais lento (compilado com -O3) ao usar o ponteiro compartilhado em todos os lugares.
fonte
std::make_shared()
? Além disso, acho as demonstrações de mau uso flagrante sendo um pouco chatas ...Em outras palavras, meu código ficará mais lento se eu usar ponteiros inteligentes e, em caso afirmativo, quanto mais lento?
Mais devagar? Provavelmente não, a menos que você esteja criando um índice enorme usando shared_ptrs e não tenha memória suficiente a ponto de seu computador começar a enrugar, como uma senhora que cai no chão por uma força insuportável de longe.
O que tornaria seu código mais lento são pesquisas 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. Digamos que você esteja iterando em uma matriz de 3 fases, cada fase possui uma matriz de 1024 elementos. Criar um
Mas você realmente quer fazer isso?smart_ptr
para esse processo pode ser um exagero, pois, uma vez que a iteração for concluída, você saberá que terá que apagá-lo. Assim, você pode ganhar memória extra por não usar umsmart_ptr
...Um único vazamento de memória pode fazer com que seu produto tenha um ponto de falha no tempo (digamos que seu programa vaze 4 megabytes a cada hora, levaria meses para quebrar um computador, no entanto, ele vai quebrar, você sabe porque o vazamento está aí) .
É como dizer "seu software tem garantia de 3 meses, então me chame para o serviço."
Então, no final, é realmente uma questão de ... você pode lidar com esse risco? Usar um ponteiro bruto para manipular sua indexação em centenas de objetos diferentes vale a pena perder o controle da memória.
Se a resposta for sim, use um ponteiro bruto.
Se você nem mesmo quiser considerar isso, a
smart_ptr
é uma solução boa, viável e incrível.fonte
smart_ptr
são realmente úteis para grandes equipesThats why you should not do this unless the function is really involved in ownership management
... ótima resposta, obrigado, votou positivamenteApenas para dar uma olhada e apenas para o
[]
operador, é ~ 5X mais lento do que o ponteiro bruto, conforme demonstrado no código a seguir, que foi compilado usandogcc -lstdc++ -std=c++14 -O0
e gerou este resultado:Estou começando a aprender c ++, tenho isso em mente: você sempre precisa saber o que está fazendo e dedicar mais tempo para saber o que os outros fizeram em seu c ++.
EDITAR
Conforme mencionado por @Mohan Kumar, forneci mais detalhes. A versão do gcc é
7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1)
, O resultado acima foi obtido quando o-O0
é usado, no entanto, quando eu uso o sinalizador '-O2', eu tenho o seguinte:Em seguida, mudou para
clang version 3.9.0
,-O0
foi:-O2
foi:O resultado do clang
-O2
é incrível.fonte
-O0
ou depure códigos. A saída será extremamente ineficiente . Sempre use pelo menos-O2
(ou-O3
hoje em dia porque alguma vetorização não é feita-O2
)free
chamada no teste malloc, edelete[]
para novo (ou tornar a variávela
estática), porque osunique_ptr
s estão chamando pordelete[]
baixo do capô, em seus destruidores.