Link estático vs link dinâmico

399

Existem razões de desempenho convincentes para escolher o link estático em vez do link dinâmico ou vice-versa em determinadas situações? Ouvi ou li o seguinte, mas não sei o suficiente sobre o assunto para confirmar sua veracidade.

1) A diferença no desempenho do tempo de execução entre o link estático e o link dinâmico é geralmente insignificante.

2) (1) não é verdadeiro se o uso de um compilador de criação de perfil que usa dados de perfil para otimizar os atalhos do programa, pois com a vinculação estática, o compilador pode otimizar o código e o código da biblioteca. Com o vínculo dinâmico, 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.

Eloff
fonte
59
"Com a vinculação estática, o compilador pode otimizar .. o código da biblioteca", mas somente se ele também compilar! Se você apenas vincular a arquivos de objetos pré-compilados, seu compilador não terá a chance de otimizá-los.
3
Se isso é verdade, então você está certo, mas há algumas dúvidas sobre o quão verdade isso é com os compiladores modernos, se alguém puder verificar isso de uma maneira ou de outra, isso seria ótimo.
Eloff
5
Com um compilador compilando para código nativo (como a maioria dos compiladores C / C ++), não há mais chance de otimização de código. Se o código for compilado em algum idioma intermediário (como .Net IL), o compilador JIT será chamado quando a biblioteca estiver sendo carregada para compilá-lo no código nativo. Essa compilação final pode melhorar cada vez mais com o tempo, à medida que o compilador JIT evolui.
Tarydon
3
@Eloff: o VS2008 faz exatamente isso com o LTCG ativado. (Embora os arquivos lib se tornem enormes ...) Eu brinquei com ele e para alguém interessado em "o que meu compilador pode fazer por mim", é incrível.
Peterchen

Respostas:

348
  • A vinculação dinâmica pode reduzir o consumo total de recursos (se mais de um processo compartilhar a mesma biblioteca (incluindo a versão em "o mesmo", é claro)). Eu acredito que este é o argumento que leva a sua presença na maioria dos ambientes. Aqui, "recursos" inclui espaço em disco, RAM e espaço em cache. Obviamente, se o seu vinculador dinâmico for insuficientemente flexível, existe o risco de o DLL ser um inferno .
  • A vinculação dinâmica significa que as correções e atualizações das bibliotecas se propagam para melhorar seu produto sem exigir que você envie nada.
  • Os plug-ins sempre pedem links dinâmicos .
  • 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 inicialização ou no modo de recuperação).
  • A vinculação estática pode facilitar a distribuição dos binários para diversos ambientes do usuário (com o custo de enviar um programa maior e com mais recursos).
  • A vinculação estática pode permitir tempos de inicialização um pouco mais rápidos , mas isso depende, até certo ponto, do tamanho e da complexidade do seu programa e dos detalhes da estratégia de carregamento do sistema operacional.

Algumas edições para incluir sugestões muito relevantes nos comentários e em outras respostas. Gostaria de observar que a maneira como você resolve isso depende muito do ambiente em que planeja executar. Os sistemas embarcados mínimos podem não ter recursos suficientes para suportar a vinculação dinâmica. Sistemas pequenos ligeiramente maiores podem muito bem oferecer suporte à vinculação dinâmica, porque sua memória é pequena o suficiente para tornar a economia de RAM da vinculação dinâmica muito atraente. Os PCs de consumo completo possuem, como observa Mark, enormes recursos, e você provavelmente pode deixar que os problemas de conveniência direcionem seu pensamento sobre esse assunto.


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

Classicamente, as bibliotecas dinâmicas exigem algum tipo de camada de cola, o que geralmente significa despacho duplo ou uma camada extra de indireção no endereçamento de funções e pode custar um pouco de velocidade (mas o tempo de chamada de função é realmente uma grande parte do seu tempo de execução ???).

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

Outra questão: tempo de carregamento. Você paga os 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 você prefira pagar até saber que precisa.

Observe que a vinculação estática versus dinâmica tradicionalmente não é um problema de otimização, porque ambos envolvem compilação separada até arquivos de objeto. No entanto, isso não é necessário: um compilador pode, em princípio, "compilar" "bibliotecas estáticas" para um formulário AST digerido inicialmente e "vinculá-las" adicionando esses ASTs aos gerados para o código principal, capacitando assim a otimização global. Nenhum dos sistemas que eu uso faz isso, por isso não posso comentar como funciona.

A maneira de responder a perguntas de desempenho é sempre testando (e use um ambiente de teste o mais próximo possível do ambiente de implantação).

dmckee --- gatinho ex-moderador
fonte
24
O consumo de recursos é basicamente o espaço do código, que com o passar do tempo é cada vez menos preocupante. Se 500K de biblioteca são compartilhados entre 5 processos, isso significa uma economia de 2MB, ou seja, menos de 0,1% dos 3GB de RAM.
Mark Ransom
3
Se a biblioteca também compartilhar o mesmo mapeamento virtual (o mesmo endereço físico e virtual em todos os processos), um link dinâmico também não salva os slots TLB na MMU do processador?
Zan Lynx
6
Além disso, um link dinâmico facilita a atualização do código da biblioteca de buggy com versões melhores.
Zan Lynx
89
@Zan Também facilita a adição de código de buggy a uma versão funcional.
6
"Os plugins sempre exigem links dinâmicos." Isso está incorreto. Alguns modelos de plugins, como o AudioUnits da Apple, podem executar o plug-in em um processo separado e usar o IPC. Essa é uma alternativa mais segura à vinculação dinâmica para plug-ins (o plug-in não pode travar o host). Sugira que a resposta seja atualizada para "Plugins podem exigir vinculação dinâmica" ou similar.
Taylor
68

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

2) Isso está correto. Com otimizações guiadas por criação de perfil, geralmente você pode obter de 10 a 15% do desempenho. Agora que a velocidade da CPU atingiu seus limites, pode valer a pena fazê-lo.

Eu acrescentaria: (3) o vinculador pode organizar funções em um agrupamento mais eficiente em cache, de modo que as perdas caras no nível do cache sejam minimizadas. Também pode afetar especialmente o tempo de inicialização dos aplicativos (com base nos resultados que eu vi no compilador Sun C ++)

E não esqueça que, com as DLLs, nenhuma eliminação de código morto pode ser executada. Dependendo do idioma, o código DLL também pode não ser o ideal. As funções virtuais são sempre virtuais porque o compilador não sabe se um cliente está substituindo-o.

Por esses motivos, caso não haja necessidade real de DLLs, basta usar a compilação estática.

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

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

Como explicado, o x86 não os possui AFAIK para mais nada, então 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.

Porém, em sistemas operacionais x86 populares como o Linux, você não precisa se preocupar se o arquivo .so / DLL não for gerado com a gccopção -fpic(o que reforça o uso das tabelas de salto indiretas). Porque se não o fizer, o código será corrigido apenas 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 memória e tocá-lo antes de poder ser usado (esvaziando a maioria dos caches, atingindo TLBs) etc. Houve um tempo quando isso foi considerado lento.

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

Não me lembro o que OS (Solaris ou FreeBSD) me deu problemas com meu sistema de compilação Unix porque eu não estava fazendo isso e se perguntou por que ele caiu até que eu aplicada -fPICpara gcc.

Lothar
fonte
4
Gosto dessa resposta, porque foi a única a abordar os pontos que levantei na pergunta.
Eloff
Seria interessante ter referências sobre os aspectos técnicos da DLL e uma comparação entre diferentes sistemas operacionais.
UncleZeiv
Parece bom, mas a velocidade da CPU definitivamente não atingiu seus limites.
Aidiakapi
67

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

Mark Ransom
fonte
17
Desde que o usuário final possa vincular novamente ao código LGPL (por exemplo, porque você fornece seu código-fonte ou arquivos de objetos compilados com o seu software), a vinculação estática é adequada . Além disso, se o seu software for para uso interno (por exemplo, para ser usado somente dentro da sua organização e não distribuído), você poderá vincular estaticamente. Isso se aplica, por exemplo, ao software de servidor, onde o servidor não é distribuído.
JBentley
3
Não entenda. Você poderia me dar mais fonte (ou elaborar mais) para apreciar o que escreveu?
Baskaya
4
@Thorn, consulte a seção de licença LGPL 4.d + e . Você precisa distribuir de uma forma que exija que o usuário faça um link ou distribuir uma biblioteca compartilhada (dinâmica).
Mark Ransom
46

Eu concordo com os pontos mencionados pelo dnmckee, além de:

  • 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.
stakx - não está mais contribuindo
fonte
6
Vale ressaltar que o compilador Go do Google compila estaticamente binários principalmente por esse motivo.
Hut8
34

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

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

Isso geralmente ocorria quando os símbolos que estavam profundamente inseridos nas bibliotecas compartilhadas tinham um nome incorreto e, portanto, não eram vinculados estaticamente. O vinculador dinâmico não resolve completamente todos os símbolos, independentemente de usar a avaliação de profundidade primeiro ou largura primeiro, para que você possa terminar com um executável vinculado dinamicamente que não possui fechamento completo.

Rob Wells
fonte
11
ponto muito bom, eu tenho tentado fazer isso recentemente com algum código que eu tenho no trabalho, mas a compilação de tudo estaticamente provou surpreendentemente chato e eu desisti
UncleZeiv
21

1 / Estive em projetos nos quais o link dinâmico versus o link estático foi comparado e a diferença não foi determinada o suficiente para mudar para o link dinâmico (eu não participei do teste, apenas conheço a conclusão)

2 / A vinculação dinâmica é frequentemente associada ao PIC (código independente da posição, código que não precisa ser modificado dependendo do endereço em que é carregado). Dependendo da arquitetura, o PIC pode trazer outra desaceleração, mas é necessário para obter o benefício de compartilhar uma biblioteca vinculada dinamicamente entre dois executáveis ​​(e até dois processos do mesmo executável se o SO usar a randomização do endereço de carga como uma medida de segurança). Não tenho certeza de que todos os sistemas operacionais permitam separar os dois conceitos, mas Solaris e Linux fazem e ISTR o HP-UX também.

3 / Já participei de outros projetos que usavam links dinâmicos para o recurso "easy patch". Mas esse "patch fácil" torna a distribuição de pequenas correções um pouco mais fácil e de uma complicada um pesadelo de versão. Muitas vezes, acabávamos precisando enviar tudo, além de rastrear problemas no site do cliente porque a versão errada era um token.

Minha conclusão é que eu usei o link estático, exceto:

  • para coisas como plugins que dependem de links dinâmicos

  • quando o compartilhamento é importante (grandes bibliotecas usadas por vários processos ao mesmo tempo, como tempo de execução C / C ++, bibliotecas de 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 de uma ABI definida que não deve ser alterada por correções.

AProgrammer
fonte
11
Alguns SOs para processadores não PIC ou PIC caros preparam bibliotecas dinâmicas para serem carregadas em um endereço específico na memória e, se puderem fazer isso, apenas mapeiam em uma cópia da biblioteca todos os processos vinculados a ela. Isso reduz muito a sobrecarga do PIC. Pelo menos o OS X e algumas distribuições Linux fazem isso, não tenho certeza sobre o Windows.
Andrew McGregor
Obrigado Andrew, eu não sabia que algumas distribuições Linux usavam isso. Você tem uma referência que eu possa seguir ou uma palavra-chave que eu possa pesquisar para saber mais? (FWIW, ouvi dizer que o Windows estava fazendo uma variante disso, mas o Windows está muito longe da minha zona de competência para que eu mencionei).
AProgramador
Acho que a palavra-chave que você procura é "pré-link" - ela prepara uma biblioteca para ser carregada rapidamente em um determinado endereço, para acelerar a inicialização do programa.
precisa saber é o seguinte
20

Isso discute em grandes detalhes sobre bibliotecas compartilhadas sobre implicações de desempenho e linux.

n
fonte
3
+1 para vincular ao tutorial de DSO do Drepper, que todo mundo que cria bibliotecas no Linux deve ler.
janneb
10

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

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

Jonathan Leffler
fonte
11
A questão LD_LIBRARY_PATHnão é exatamente um obstáculo para o uso de bibliotecas compartilhadas, pelo menos não no GNU / Linux. Por exemplo, se você colocar as bibliotecas compartilhadas no diretório ../lib/relativo ao arquivo do programa, com a cadeia de ferramentas GNU, a opção vinculador -rpath $ORIGIN/../libespecificará a pesquisa na biblioteca a partir desse local relativo. Em seguida, você pode realocar facilmente o aplicativo junto com todas as bibliotecas compartilhadas associadas. Usando esse truque, também não há problema em ter várias versões do aplicativo e das bibliotecas (supondo que elas estejam relacionadas, caso contrário, você poderia usar links simbólicos).
Foof
> para processos com privilégios de root. Eu acho que você está falando sobre programas setuid executados por usuários não raiz - caso contrário, isso não faz sentido. E um binário setuid com bibliotecas em locais fora do padrão é estranho - mas como apenas o root pode instalar esses programas, ele também pode editar /etc/ld.so.confpara esse caso.
precisa saber é o seguinte
10

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

Hans Passant
fonte
11
Na verdade, eu não comparei a diferença nas velocidades de compilação, mas vincularia dinamicamente se fosse significativamente mais rápido. O Boost faz coisas ruins o suficiente nos meus tempos de compilação.
Eloff 5/01/10
9

O melhor exemplo de vinculação dinâmica é quando a biblioteca depende do hardware usado. Antigamente, a biblioteca de matemática C era decidida como dinâmica, para que cada plataforma pudesse usar todos os recursos do processador para otimizá-la.

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

Arne
fonte
8

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

Além disso, consulte DLL Hell . Esse é o cenário em que a DLL que o sistema operacional carrega não é a que acompanha o aplicativo ou a versão que ele espera.

Thomas Matthews
fonte
11
Importante notar que há uma série de contramedidas para evitar o DLL Hell.
Ocd 26/12/12
5

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

Com a vinculação estática, você não apenas precisa recriar a biblioteca, mas também deve vincular novamente e redistribuir o executável. Se a biblioteca for usada apenas em um executável, isso pode não ser um problema. Porém, quanto mais executáveis ​​precisarem ser vinculados e redistribuídos, maior será a dor.

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

R Samuel Klatchko
fonte
2

A vinculação estática fornece apenas um único exe, para fazer uma alteração, você precisa recompilar todo o programa. Considerando que, no vínculo dinâmico, é necessário fazer alterações apenas na dll e, quando você executa o exe, as alterações são capturadas no tempo de execução. É mais fácil fornecer atualizações e correções de bugs pelo vínculo dinâmico (por exemplo, janelas).

Govardhan Murali
fonte
2

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 ao que costuma ser chamado de "sistemas embarcados", muitos dos quais agora usam cada vez mais sistemas operacionais de uso geral, e esses sistemas são usados ​​para tudo o que se possa imaginar.

Um exemplo extremamente comum são dispositivos usando sistemas GNU / Linux usando Busybox . Eu levei isso ao extremo com o NetBSD construindo uma imagem de sistema inicializável do i386 (32 bits) que inclui um kernel e seu sistema de arquivos raiz, o último que contém um único crunchgenbinário (by ) vinculado à estática com links físicos para todos os programas que contêm todos (até a última contagem 274) dos programas padrão de sistema com todos os recursos (a maioria, exceto a cadeia de ferramentas) e têm menos de 20 mega bytes de tamanho (e provavelmente são executados confortavelmente em um sistema com apenas 64 MB de memória (mesmo com o sistema de arquivos raiz descompactado e totalmente na RAM), embora não tenha conseguido encontrar um tão pequeno para testá-lo.

Já foi mencionado em postagens anteriores que o tempo de inicialização de binários vinculados à estática é mais rápido (e pode ser muito mais rápido), mas isso é apenas parte da imagem, especialmente quando todo o código de objeto está vinculado ao mesmo e, mais ainda, quando o sistema operacional oferece suporte à paginação por demanda de código diretamente do arquivo executável. Nesse cenário ideal, o tempo de inicialização dos programas é literalmente desprezível, pois quase todas as páginas de código já estarão na memória e serão usadas pelo shell (e initpor outros processos em segundo plano que possam estar em execução), mesmo que o programa solicitado não tenha sido executado. já foi executada desde a inicialização, pois talvez apenas uma página de memória precise ser carregada para atender aos requisitos de tempo de execução do programa.

No entanto, essa ainda não é a história toda. Também costumo criar e usar as instalações do sistema operacional NetBSD para todos os meus sistemas de desenvolvimento vinculando estática todos os binários. Mesmo que isso exija uma quantidade enorme de espaço em disco (~ 6,6 GB no total para x86_64 com tudo, incluindo cadeia de ferramentas e link estático X11) (especialmente se você mantiver tabelas de símbolos de depuração completas disponíveis para todos os programas, outros ~ 2,5 GB), o resultado ainda será é mais rápido no geral e, para algumas tarefas, usa menos memória do que um sistema típico vinculado à dinâmica que pretende compartilhar páginas de códigos da biblioteca. O disco é barato (mesmo o disco rápido), e a memória para armazenar em cache os arquivos de disco usados ​​com freqüência também é relativamente barata, mas os ciclos da CPU realmente não são, e pagando o ld.socusto de inicialização para cada processo que inicia a cadao tempo que ele inicia levará horas e horas de ciclos da CPU para tarefas que exigem o início de muitos processos, especialmente quando os mesmos programas são usados ​​repetidamente, como compiladores em um sistema de desenvolvimento. Programas de cadeia de ferramentas vinculados à estática podem reduzir o tempo de construção de várias arquiteturas de todo o sistema operacional para meus sistemas em horas . Ainda tenho que construir a cadeia de ferramentas em meu único crunchgenbinário, mas suspeito que, quando o fizer, haverá mais horas de tempo de compilação economizadas por causa da vitória no cache da CPU.

Greg A. Woods
fonte
2

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

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

(DLL = biblioteca de vínculo dinâmico )

Os executáveis ​​vinculados dinamicamente são compilados mais rapidamente e não consomem muito recursos.

Nykal
fonte
0

Static linking é um processo em tempo de compilação quando um conteúdo vinculado é copiado no binário primário e se torna um único binário.

Contras:

  • tempo de compilação é maior
  • binário de saída é maior

Dynamic linkingé um processo em tempo de execução quando um conteúdo vinculado é carregado. Essa técnica permite:

  • atualizar o binário vinculado sem recompilar o principal que aumenta a ABIestabilidade [Sobre]
  • tenha uma única cópia compartilhada

Contras:

  • o horário de início é mais lento (o conteúdo vinculado deve ser copiado)
  • erros do vinculador são gerados em tempo de execução
yoAlex5
fonte