Vinculação Estática vs Vinculação Dinâmica

Existe algum motivo convincente de desempenho para escolher a vinculação estática sobre a vinculação dinâmica ou vice-versa em determinadas situações? Eu ouvi ou li o seguinte, mas eu não sei o suficiente sobre o assunto para garantir sua veracidade.

1) A diferença no desempenho em tempo de execução entre a vinculação estática e a vinculação dinâmica é geralmente insignificante.

2) (1) não é verdade se estiver usando um compilador de criação de perfil que usa dados de perfil para otimizar hotpaths do programa porque com a vinculação estática, o compilador pode otimizar seu código e o código da biblioteca. Com a vinculação dinâmica, apenas seu código pode ser otimizado. Se a maior parte do tempo é gasta executando o código da biblioteca, isso pode fazer uma grande diferença. Caso contrário, (1) ainda se aplica.

    • A vinculação dinâmica pode reduzir o consumo total de resources (se mais de um processo compartilhar a mesma biblioteca (incluindo a versão em “the same”, é claro)). Acredito que este é o argumento que impulsiona sua presença na maioria dos ambientes. Aqui “resources” inclui espaço em disco, RAM e espaço em cache. Naturalmente, se o seu vinculador dynamic é insuficientemente flexível, existe o risco de um inferno na DLL .
    • Links dynamics significam que correções de bugs e upgrades para bibliotecas se propagam para melhorar seu produto sem que você precise enviar nada.
    • Plugins sempre pedem links dynamics .
    • Ligação estática , significa que você pode saber que o código será executado em ambientes muito limitados (no início do processo de boot ou no modo de recuperação).
    • A vinculação estática pode facilitar a distribuição de binários a diversos ambientes do usuário (com o custo de enviar um programa grande e com mais resources).
    • A vinculação estática pode permitir tempos de boot um pouco mais rápidos , mas isso depende em algum grau do tamanho e da complexidade do seu programa e dos detalhes da estratégia de carregamento do sistema operacional.

    Algumas edições incluem as sugestões muito relevantes nos comentários e em outras respostas. Eu gostaria de observar que a maneira como você quebra isso depende muito do ambiente em que você planeja rodar. Sistemas embarcados mínimos podem não ter resources suficientes para suportar links dynamics. Sistemas pequenos um pouco maiores podem oferecer suporte à vinculação, porque sua memory é pequena o suficiente para tornar a economia de RAM de vinculação dinâmica muito atraente. Os PCs de consumo completos têm, como observa Mark, enormes resources, e você provavelmente pode deixar que os problemas de conveniência o levem a pensar sobre esse assunto.


    Para resolver os problemas de desempenho e eficiência: depende .

    Classicamente, as bibliotecas dinâmicas requerem algum tipo de camada de cola, o que muitas vezes significa expedição dupla ou uma camada extra de indireto no endereçamento da function e pode custar um pouco de velocidade (mas o tempo de chamada da function é realmente uma grande parte do seu tempo de execução).

    No entanto, se você estiver executando vários processos que chamam muito a mesma biblioteca, você pode salvar linhas de cache (e, portanto, ganhar no desempenho de execução) ao usar vinculação dinâmica relativa usando vinculação estática. (A menos que os sistemas operacionais modernos sejam inteligentes o suficiente para notar segmentos idênticos em binários vinculados estaticamente. Parece difícil, alguém sabe?)

    Outro problema: tempo de carregamento. Você paga custos de carregamento em algum momento. Quando você paga esse custo, depende de como o sistema operacional funciona e de qual link você usa. Talvez prefira deixar de pagá-lo até saber que precisa.

    Observe que a vinculação estática versus dinâmica tradicionalmente não é um problema de otimização, porque ambas envolvem compilation separada para arquivos de object. No entanto, isso não é necessário: um compilador pode, em princípio, “compilar” “bibliotecas estáticas” para um formulário AST typescript inicialmente e “vinculá-las” adicionando essas ASTs àquelas geradas para o código principal, fortalecendo assim a otimização global. Nenhum dos sistemas que uso faz isso, então não posso comentar como funciona.

    A maneira de responder a questões de desempenho é sempre testando (e usando um ambiente de teste o mais parecido com o ambiente de implementação possível).

    A binding dinâmica é a única maneira prática de atender a alguns requisitos de licença, como a LGPL .

    1) baseia-se no fato de que chamar uma function DLL está sempre usando um salto indireto extra. Hoje, isso geralmente é insignificante. Dentro da DLL há mais alguma sobrecarga em CPUs i386, porque elas não podem gerar código independente de posição. Em amd64, saltos podem ser relativos ao contador de programa, então isso é uma grande melhoria.

    2) Isso está correto. Com as otimizações guiadas pela criação de perfis, você geralmente consegue um desempenho de 10 a 15%. Agora que a velocidade da CPU atingiu seus limites, talvez valha a pena fazê-lo.

    Gostaria de acrescentar: (3) o vinculador pode organizar funções em um agrupamento mais eficiente em cache, de modo que os altos níveis de perda de cache sejam minimizados. Também pode afetar especialmente o tempo de boot dos aplicativos (com base nos resultados que vi com o compilador Sun C ++)

    E não se esqueça que com a eliminação de código morto da DLL pode ser realizada. Dependendo do idioma, o código DLL pode não ser ideal. As funções virtuais são sempre virtuais porque o compilador não sabe se um cliente está sobrescrevendo-o.

    Por esses motivos, caso não haja necessidade real de DLLs, use apenas a compilation estática.

    EDITAR (para responder ao comentário, por sublinhado do usuário)

    Aqui está um bom recurso sobre o problema de código independente de posição http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

    Como explicado, o x86 não tem o AFAIK para nada além de intervalos de salto de 15 bits e não para saltos e chamadas incondicionais. É por isso que funções (de geradores) com mais de 32K sempre foram um problema e precisavam de trampolins incorporados.

    Mas no popular SO x86 como o Linux, você não precisa se preocupar se o arquivo SO / DLL não é gerado com o gcc switch -fpic (que impõe o uso das tabelas de salto indiretas). Porque, se você não o fizer, o código será corrigido como um vinculador normal o realocaria. Mas, ao fazer isso, torna o segmento de código não compartilhável e precisaria de um mapeamento completo do código do disco para a memory e tocá-lo antes de poder ser usado (esvaziando a maioria dos caches, atingindo TLBs) etc. quando isso foi considerado lento … muito devagar.

    Então você não teria mais nenhum benefício.

    Eu não lembro o que o sistema operacional (Solaris ou FreeBSD) me deu problemas com o meu sistema de compilation Unix porque eu não estava fazendo isso e me perguntei porque ele travou até que eu apliquei -fPIC ao gcc .

    Concordo com os pontos dnmckee menciona, mais:

    • Os aplicativos vinculados estaticamente podem ser mais fáceis de implantar, pois há menos ou nenhuma dependência de arquivo adicional (.dll / .so) que pode causar problemas quando eles estão ausentes ou instalados no lugar errado.

    Um motivo para fazer uma compilation estaticamente vinculada é verificar se você tem fechamento completo para o executável, ou seja, se todas as referências de símbolo foram resolvidas corretamente.

    Como parte de um grande sistema que estava sendo construído e testado usando continuous integration, os testes de regressão noturna foram executados usando uma versão dos executáveis ​​vinculada estaticamente. Ocasionalmente, veríamos que um símbolo não seria resolvido e o link estático falharia, mesmo que o executável vinculado dinamicamente fosse vinculado com êxito.

    Isso geralmente ocorria quando os símbolos que estavam profundamente encheckboxdos nas bibliotecas compartilhadas tinham um nome com erro de digitação e, portanto, não vinculavam estaticamente. O vinculador dynamic não resolve completamente todos os símbolos, independentemente de usar a avaliação em profundidade ou em primeiro lugar, para que você possa terminar com um executável vinculado dinamicamente que não tenha fechamento completo.

    1 / Eu estive em projetos onde a vinculação dinâmica vs vinculação estática foi comparada e a diferença não foi determinada pequena o suficiente para mudar para vinculação dinâmica (eu não fiz parte do teste, só sei a conclusão)

    2 / Vinculação dinâmica é frequentemente associada ao PIC (Position Independent Code, código que não precisa ser modificado dependendo do endereço no qual ele está carregado). Dependendo da arquitetura, o PIC pode trazer outra lentidão, mas é necessário para obter o benefício de compartilhar uma biblioteca vinculada dinamicamente entre dois executáveis ​​(e até mesmo dois processos do mesmo executável, se o sistema operacional usar a aleatoriedade do endereço de carregamento como medida de segurança). Não tenho certeza de que todos os sistemas operacionais permitem separar os dois conceitos, mas o Solaris e o Linux fazem o ISTR e o HP-UX também.

    3 / Eu estive em outros projetos que usavam links dynamics para o recurso “easy patch”. Mas esse “patch fácil” torna a distribuição de pequenas correções um pouco mais fácil e complicada, um pesadelo de versionamento. Muitas vezes acabamos tendo que empurrar tudo além de ter que rastrear problemas no site do cliente porque a versão errada era simbólica.

    Minha conclusão é que usei o link estático com exceção:

    • para coisas como plugins que dependem de binding dinâmica

    • quando o compartilhamento é importante (grandes bibliotecas usadas por vários processos ao mesmo tempo, como tempo de execução C / C ++, bibliotecas GUI, … que geralmente são gerenciadas independentemente e para as quais a ABI é estritamente definida)

    Se alguém quiser usar o “patch fácil”, eu diria que as bibliotecas precisam ser gerenciadas como as grandes bibliotecas acima: elas devem ser quase independentes com uma ABI definida que não deve ser alterada por correções.

    Isso discute em detalhes sobre bibliotecas compartilhadas no linux e impliaction performance.

    Em sistemas do tipo Unix, a vinculação dinâmica pode dificultar a vida do ‘root’ para usar um aplicativo com as bibliotecas compartilhadas instaladas em locais fora do caminho. Isso ocorre porque o vinculador dynamic geralmente não presta atenção ao LD_LIBRARY_PATH ou seu equivalente para processos com privilégios de root. Às vezes, a vinculação estática salva o dia.

    Como alternativa, o processo de instalação deve localizar as bibliotecas, mas isso pode dificultar a coexistência de várias versões do software na máquina.

    É bem simples, na verdade. Quando você faz uma alteração no seu código-fonte, você quer esperar 10 minutos para ele ou 20 segundos? Vinte segundos é tudo que posso suportar. Além disso, eu tiro a espada ou começo a pensar em como posso usar compilation e links separados para trazê-la de volta à zona de conforto.

    A vinculação dinâmica requer tempo extra para o sistema operacional encontrar a biblioteca dinâmica e carregá-la. Com a binding estática, tudo está junto e é uma carga única na memory.

    Além disso, veja DLL Hell . Este é o cenário em que a DLL que o sistema operacional carrega não é aquela que acompanha seu aplicativo ou a versão que seu aplicativo espera.

    O melhor exemplo de vinculação dinâmica é quando a biblioteca depende do hardware usado. Nos tempos antigos, a biblioteca de matemática C foi decidida a ser dinâmica, de modo que cada plataforma pode usar todos os resources do processador para otimizá-lo.

    Um exemplo ainda melhor pode ser o OpenGL. O OpenGl é uma API implementada de forma diferente pela AMD e NVidia. E você não é capaz de usar uma implementação NVidia em uma placa AMD, porque o hardware é diferente. Você não pode vincular o OpenGL estaticamente ao seu programa, por causa disso. A vinculação dinâmica é usada aqui para permitir que a API seja otimizada para todas as plataformas.

    Outra questão ainda não discutida é a correção de bugs na biblioteca.

    Com a vinculação estática, você não precisa apenas recriar a biblioteca, mas terá que vincular novamente e redistribuir o executável. Se a biblioteca é usada apenas em um executável, isso pode não ser um problema. Mas quanto mais executáveis ​​precisam ser revincados e redistribuídos, maior é a dor.

    Com a vinculação dinâmica, você apenas reconstrói e redistribui a biblioteca dinâmica e está pronto.

    A vinculação estática oferece apenas um único exe, a fim de fazer uma alteração que você precisa para recompilar todo o seu programa. Considerando que na binding dinâmica você precisa fazer alterações apenas para a dll e quando você executa o seu exe, as alterações seriam apanhadas em tempo de execução.É mais fácil fornecer atualizações e correções de bugs por vinculação dinâmica (por exemplo: windows).

    Há um número vasto e crescente de sistemas em que um nível extremo de vinculação estática pode ter um enorme impacto positivo nos aplicativos e no desempenho do sistema.

    Refiro-me aos chamados “sistemas embarcados”, muitos dos quais estão usando cada vez mais sistemas operacionais de propósito geral, e esses sistemas são usados ​​para tudo que se possa imaginar.

    Um exemplo extremamente comum são dispositivos que usam sistemas GNU / Linux usando o Busybox . Eu levei isso ao extremo com o NetBSD construindo uma imagem inicializável do sistema i386 (32 bits) que inclui um kernel e seu sistema de arquivos raiz, o último que contém um único binário estático (por crunchgen ) com hard-links para todos os programas que ele mesmo contém todos (bem na última contagem 274) dos programas padrão do sistema completo (a maioria exceto o toolchain), e tem menos de 20 mega bytes (e provavelmente roda muito confortavelmente em um sistema com apenas 64MB de memory (mesmo com o sistema de arquivos raiz descompactado e totalmente na RAM), embora eu não tenha conseguido encontrar um tão pequeno para testá-lo).

    Foi mencionado em posts anteriores que o tempo de boot de um binários vinculados estáticos é mais rápido (e pode ser muito mais rápido), mas isso é apenas parte da imagem, especialmente quando todo o código de object está vinculado ao mesmo arquivo, e ainda mais especialmente quando o sistema operacional suporta paginação de demanda de código direto do arquivo executável. Neste cenário ideal, o tempo de boot dos programas é literalmente insignificante, já que quase todas as páginas de código já estarão na memory e estarão em uso pelo shell (e quaisquer outros processos em segundo plano que possam estar em execução), mesmo se o programa solicitado nunca foi executado desde a boot, pois talvez apenas uma página de memory precise ser carregada para atender aos requisitos de tempo de execução do programa.

    No entanto, isso ainda não é toda a história. Eu também geralmente construo e uso as instalações do sistema operacional NetBSD para todos os meus sistemas de desenvolvimento por meio da vinculação estática de todos os binários. Mesmo que isso consuma uma quantidade enorme de espaço em disco (~ 6.6GB total para x86_64 com tudo, incluindo toolchain e X11 static-linked) (especialmente se manter tabelas de símbolos de debugging disponíveis para todos os outros programas ~ 2.5GB), o resultado ainda corre mais rápido no geral e, para algumas tarefas, usa até menos memory do que um sistema dynamic com link típico que pretende compartilhar páginas de códigos de bibliotecas. O disco é barato (mesmo disco rápido), e a memory para armazenar em cache arquivos de disco freqüentemente usados ​​também é relativamente barata, mas ciclos de CPU realmente não são, e pagar o custo de boot de ld.so para todo processo que começa toda vez que isto inicia levará horas e horas de ciclos de CPU longe de tarefas que requerem iniciar muitos processos, especialmente quando os mesmos programas são usados ​​repetidamente, como compiladores em um sistema de desenvolvimento. Os programas de conjunto de ferramentas com link estático podem reduzir o tempo de construção de várias arquiteturas de todo o sistema operacional para meus sistemas em horas . Eu ainda tenho que construir o conjunto de ferramentas em meu único binário crunchgen ‘ed, mas eu suspeito que quando eu fizer haverá mais horas de tempo de compilation salvo por causa da vitória do cache da CPU.

    Vinculação estática inclui os arquivos que o programa precisa em um único arquivo executável.

    Vinculação dinâmica é o que você consideraria o usual, faz um executável que ainda requer DLLs e tal estar no mesmo diretório (ou as DLLs podem estar na pasta do sistema).

    (DLL = biblioteca de links dynamics )

    Os executáveis ​​dinamicamente vinculados são compilados mais rapidamente e não são tão pesados ​​quanto os resources.