Czy istnieją jakieś istotne powody, dla których warto wybrać łączenie statyczne zamiast dynamicznego lub odwrotnie w niektórych sytuacjach? Słyszałem lub czytałem poniższe, ale nie wiem wystarczająco dużo na ten temat, by ręczyć za ich prawdziwość.
1) Różnica w wydajności środowiska wykonawczego między łączeniem statycznym a łączeniem dynamicznym jest zwykle nieistotna.
2) (1) nie jest prawdziwe, jeśli używasz kompilatora profilowania, który wykorzystuje dane profilu do optymalizacji ścieżek programu, ponieważ dzięki statycznemu łączeniu kompilator może zoptymalizować zarówno kod, jak i kod biblioteki. Dzięki dynamicznemu łączeniu tylko Twój kod może być zoptymalizowany. Jeśli spędza się większość czasu na uruchamianiu kodu biblioteki, może to mieć dużą różnicę. W przeciwnym razie (1) nadal obowiązuje.
Odpowiedzi:
Niektóre zmiany zawierają bardzo trafne sugestie w komentarzach i innych odpowiedziach. Chciałbym zauważyć, że sposób, w jaki się na to włamujesz, zależy w dużej mierze od środowiska, w którym zamierzasz działać. Minimalne systemy wbudowane mogą nie mieć wystarczających zasobów do obsługi dynamicznego łączenia. Nieco większe małe systemy mogą obsługiwać dynamiczne łączenie, ponieważ ich pamięć jest na tyle mała, że oszczędność pamięci RAM z dynamicznego łączenia jest bardzo atrakcyjna. W pełni rozwinięte komputery konsumenckie mają, jak zauważa Mark, ogromne zasoby, i prawdopodobnie możesz pozwolić, aby problemy z wygodą wpłynęły na twoje myślenie w tej sprawie.
Aby rozwiązać problemy z wydajnością i wydajnością: to zależy .
Klasycznie biblioteki dynamiczne wymagają pewnego rodzaju warstwy kleju, co często oznacza podwójne wysyłanie lub dodatkową warstwę pośredniczącą w adresowaniu funkcji i może kosztować trochę prędkości (ale czy czas wywoływania funkcji jest rzeczywiście dużą częścią twojego czasu działania ???).
Jeśli jednak uruchomisz wiele procesów, z których wszystkie często wywołują tę samą bibliotekę, możesz w końcu zapisać linie pamięci podręcznej (a tym samym zyskać na wydajności), używając dynamicznego linkowania w porównaniu do statycznego linkowania. (Chyba że współczesne systemy operacyjne są wystarczająco inteligentne, aby zauważyć identyczne segmenty w statycznie połączonych plikach binarnych. Wydaje się trudne, ktoś wie?)
Kolejny problem: czas ładowania. W pewnym momencie ponosisz koszty załadunku. Kiedy płacisz, ten koszt zależy od tego, jak działa system operacyjny, a także od tego, z jakiego łącza korzystasz. Może wolisz odłożyć płacenie, dopóki nie będziesz wiedział, że go potrzebujesz.
Zauważ, że łączenie statyczne z dynamicznym tradycyjnie nie jest problemem optymalizacji, ponieważ oba wymagają osobnej kompilacji w dół do plików obiektowych. Nie jest to jednak wymagane: kompilator może co do zasady „kompilować” „biblioteki statyczne” do przetworzonego formularza AST i „łączyć” je, dodając te AST do generowanych dla głównego kodu, zapewniając w ten sposób globalną optymalizację. Żaden z używanych przeze mnie systemów tego nie robi, więc nie mogę komentować, jak dobrze działa.
Odpowiedzią na pytania dotyczące wydajności jest zawsze testowanie (i używanie środowiska testowego tak bardzo, jak to możliwe).
źródło
1) polega na tym, że wywołanie funkcji DLL zawsze wykorzystuje dodatkowy skok pośredni. Dzisiaj jest to zwykle nieistotne. Wewnątrz biblioteki DLL występuje więcej obciążenia na procesorach i386, ponieważ nie mogą one generować kodu niezależnego od pozycji. Na amd64 skoki mogą być względne względem licznika programu, więc jest to ogromna poprawa.
2) To prawda. Dzięki optymalizacjom prowadzonym przez profilowanie zwykle można uzyskać około 10-15 procent wydajności. Teraz, gdy szybkość procesora osiągnęła już limit, warto to zrobić.
Dodałbym: (3) linker może organizować funkcje w bardziej wydajnym grupowaniu pamięci podręcznej, aby zminimalizować kosztowne pomyłki w poziomie pamięci podręcznej. Może to również w szczególności wpłynąć na czas uruchamiania aplikacji (na podstawie wyników, które widziałem z kompilatorem Sun C ++)
I nie zapominaj, że w bibliotekach DLL nie można wyeliminować martwego kodu. W zależności od języka kod DLL może również nie być optymalny. Funkcje wirtualne są zawsze wirtualne, ponieważ kompilator nie wie, czy klient go nadpisuje.
Z tych powodów, na wypadek, gdyby nie było potrzeby używania bibliotek DLL, wystarczy użyć kompilacji statycznej.
EDYCJA (aby odpowiedzieć na komentarz, podkreślenie użytkownika)
Oto dobry zasób na temat problemu z kodem niezależnym od pozycji http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/
Jak wyjaśniono, x86 nie ma ich AFAIK dla niczego innego niż 15-bitowe zakresy skoków, a nie dla bezwarunkowych skoków i wywołań. Dlatego funkcje (z generatorów) mające ponad 32 KB zawsze stanowiły problem i wymagały wbudowanych trampolin.
Ale w popularnym systemie operacyjnym x86, takim jak Linux, nie trzeba się przejmować, jeśli plik .so / DLL nie zostanie wygenerowany za pomocą
gcc
przełącznika-fpic
(co wymusza użycie pośrednich tabel skoków). Ponieważ jeśli tego nie zrobisz, kod zostanie naprawiony tak, jak zwykły linker go przeniesie. Ale czyniąc to, segment kodu nie jest współdzielony i potrzebowałoby pełnego odwzorowania kodu z dysku do pamięci i dotknięcia go, zanim będzie można go użyć (opróżnienie większości pamięci podręcznych, uderzenie w TLB) itp. Był czas kiedy uznano to za wolne.Więc nie miałbyś już żadnych korzyści.
Nie przypominam sobie, co dało OS (Solaris czy FreeBSD) mi problemy z moim systemie build Unix, bo po prostu nie było to robi i zastanawiał się, dlaczego rozbił aż stosowane
-fPIC
dogcc
.źródło
Dynamiczne łączenie jest jedynym praktycznym sposobem spełnienia niektórych wymagań licencyjnych, takich jak LGPL .
źródło
Zgadzam się z punktami wspomnianymi przez dnmckee oraz:
źródło
Jednym z powodów wykonania kompilacji statycznie powiązanej jest sprawdzenie, czy masz pełne zamknięcie pliku wykonywalnego, tj. Czy wszystkie odwołania do symboli zostały poprawnie rozwiązane.
W ramach dużego systemu, który był budowany i testowany przy użyciu ciągłej integracji, nocne testy regresji były przeprowadzane przy użyciu statycznie powiązanej wersji plików wykonywalnych. Czasami widzieliśmy, że symbol się nie rozwiąże, a łącze statyczne ulegnie awarii, nawet jeśli dynamicznie połączony plik wykonywalny zostanie pomyślnie połączony.
Miało to zwykle miejsce, gdy symbole, które były głęboko osadzone w udostępnionych bibliotekach, miały błędnie napisaną nazwę i dlatego nie łączyły się statycznie. Dynamiczny linker nie rozpoznaje w pełni wszystkich symboli, bez względu na to, czy zastosowano ocenę głębokości, czy szerokość, więc możesz zakończyć z dynamicznie połączonym plikiem wykonywalnym, który nie ma pełnego zamknięcia.
źródło
1 / Brałem udział w projektach, w których porównano dynamiczne łączenie z łączeniem statycznym, a różnica nie była wystarczająco mała, aby przejść na łączenie dynamiczne (nie brałem udziału w teście, po prostu znam wniosek)
2 / Dynamiczne łączenie jest często powiązane z PIC (kod niezależny od pozycji, kod, który nie musi być modyfikowany w zależności od adresu, pod którym jest ładowany). W zależności od architektury PIC może przynieść kolejne spowolnienie, ale jest potrzebne w celu uzyskania korzyści z dzielenia się dynamicznie połączoną biblioteką między dwoma plikami wykonywalnymi (a nawet dwoma procesami tego samego pliku wykonywalnego, jeśli system operacyjny używa losowego adresu obciążenia jako środka bezpieczeństwa). Nie jestem pewien, czy wszystkie systemy operacyjne pozwalają rozdzielić te dwie koncepcje, ale Solaris i Linux to robią, a ISTR tak samo jak HP-UX.
3 / Brałem udział w innych projektach, które korzystały z dynamicznego linkowania dla funkcji „łatwej łatki”. Ale ta „łatwa łatka” sprawia, że dystrybucja małej poprawki jest nieco łatwiejsza, a skomplikowanej koszmarem wersjonowania. Często kończyliśmy się tym, że musieliśmy wszystko przepychać, a także musieliśmy śledzić problemy w witrynie klienta, ponieważ niewłaściwa wersja była tokenem.
Mój wniosek jest taki, że użyłem linkowania statycznego, z wyjątkiem:
dla rzeczy takich jak wtyczki, które zależą od dynamicznego linkowania
gdy współużytkowanie jest ważne (duże biblioteki używane przez wiele procesów jednocześnie, takie jak środowisko wykonawcze C / C ++, biblioteki GUI, ... które często są zarządzane niezależnie i dla których ABI jest ściśle określone)
Jeśli ktoś chce skorzystać z „łatwej łatki”, argumentowałbym, że bibliotekami należy zarządzać tak, jak duże biblioteki powyżej: muszą one być prawie niezależne z określonym ABI, którego nie można zmieniać za pomocą poprawek.
źródło
Ten omówić szczegółowo o współdzielonych bibliotek implikacji Linux i wydajności.
źródło
W systemach uniksowych dynamiczne łączenie może utrudnić „rootowi” korzystanie z aplikacji z bibliotekami współdzielonymi zainstalowanymi w nietypowych lokalizacjach. Wynika to z faktu, że dynamiczny linker zasadniczo nie zwraca uwagi na LD_LIBRARY_PATH lub jego odpowiednik dla procesów z uprawnieniami roota. Czasami więc statyczne łączenie ratuje dzień.
Alternatywnie proces instalacji musi zlokalizować biblioteki, ale może to utrudnić współistnienie wielu wersji oprogramowania na komputerze.
źródło
LD_LIBRARY_PATH
to, że nie jest dokładnie przeszkodą w korzystaniu z bibliotek współdzielonych, przynajmniej nie w GNU / Linux. Np. Jeśli umieścisz współdzielone biblioteki w katalogu../lib/
względem pliku programu, to w łańcuchu narzędzi GNU opcja linkera-rpath $ORIGIN/../lib
określi przeszukiwanie biblioteki z tej względnej lokalizacji. Następnie możesz łatwo przenieść aplikację wraz ze wszystkimi powiązanymi bibliotekami współdzielonymi. Dzięki tej sztuczce nie ma problemu z posiadaniem wielu wersji aplikacji i bibliotek (zakładając, że są one powiązane, jeśli nie, możesz użyć dowiązań symbolicznych)./etc/ld.so.conf
w tym przypadku.To naprawdę bardzo proste. Kiedy wprowadzasz zmiany w kodzie źródłowym, czy chcesz poczekać 10 minut na kompilację, czy 20 sekund? Dwadzieścia sekund to wszystko, co mogę znieść. Poza tym albo wyjmuję miecz, albo zaczynam myśleć o tym, jak mogę użyć osobnej kompilacji i linków, aby przywrócić go do strefy komfortu.
źródło
Najlepszym przykładem dynamicznego łączenia jest sytuacja, gdy biblioteka zależy od używanego sprzętu. W czasach starożytnych biblioteka matematyki C była dynamiczna, aby każda platforma mogła wykorzystać wszystkie możliwości procesora do jej optymalizacji.
Jeszcze lepszym przykładem może być OpenGL. OpenGl to interfejs API, który jest różnie implementowany przez AMD i NVidia. Nie możesz użyć implementacji NVidia na karcie AMD, ponieważ sprzęt jest inny. Z tego powodu nie można połączyć statycznie OpenGL ze swoim programem. Stosowane jest tutaj dynamiczne łączenie, aby umożliwić optymalizację interfejsu API dla wszystkich platform.
źródło
Łączenie dynamiczne wymaga dodatkowego czasu, aby system operacyjny znalazł bibliotekę dynamiczną i załadował ją. Dzięki łączeniu statycznemu wszystko jest razem i jest to jednorazowe ładowanie do pamięci.
Zobacz także DLL Hell . W tym scenariuszu biblioteka DLL ładowana przez system operacyjny nie jest biblioteką dostarczoną z aplikacją ani wersją oczekiwaną przez aplikację.
źródło
Innym zagadnieniem, które nie zostało jeszcze omówione, jest usuwanie błędów w bibliotece.
Dzięki statycznemu łączeniu nie tylko musisz odbudować bibliotekę, ale także będzie musiał ponownie połączyć i rozpowszechnić plik wykonywalny. Jeśli biblioteka jest używana tylko w jednym pliku wykonywalnym, może to nie stanowić problemu. Ale im więcej plików wykonywalnych wymaga ponownego połączenia i redystrybucji, tym większy jest ból.
Dzięki dynamicznemu łączeniu po prostu odbudowujesz i redystrybuujesz bibliotekę dynamiczną i gotowe.
źródło
linkowanie statyczne daje tylko jeden plik exe, w celu dokonania zmiany potrzebnej do ponownej kompilacji całego programu. Podczas gdy w dynamicznym łączeniu musisz wprowadzać zmiany tylko w bibliotece dll, a kiedy uruchomisz exe, zmiany będą pobierane w czasie wykonywania. Łatwiej jest dostarczać aktualizacje i poprawki błędów przez dynamiczne łączenie (np. Windows).
źródło
Istnieje ogromna i rosnąca liczba systemów, w których ekstremalny poziom statycznego łączenia może mieć ogromny pozytywny wpływ na aplikacje i wydajność systemu.
Mam na myśli tak zwane „systemy wbudowane”, z których wiele coraz częściej korzysta z systemów operacyjnych ogólnego zastosowania, a systemy te są wykorzystywane do wszystkiego, co można sobie wyobrazić.
Niezwykle częstym przykładem są urządzenia korzystające z systemów GNU / Linux korzystających z Busybox . Doszedłem do skrajności w NetBSD , budując bootowalny obraz systemu i386 (32-bitowy), który zawiera zarówno jądro, jak i jego główny system plików, ten ostatni, który zawiera pojedynczy
crunchgen
plik binarny połączony statycznie z twardymi dowiązaniami do wszystkie programy, które same zawierają wszystkie (w końcu 274) standardowych pełnowartościowych programów systemowych (większość z wyjątkiem łańcucha narzędzi), i mają mniej niż 20 megabajtów (i prawdopodobnie działają bardzo wygodnie w systemie z zaledwie 64 MB pamięci (nawet przy głównym systemie plików nieskompresowanym i całkowicie w pamięci RAM), chociaż nie byłem w stanie znaleźć takiego tak małego do przetestowania).We wcześniejszych postach wspomniano, że czas uruchamiania plików binarnych połączonych statycznie jest krótszy (i może być znacznie szybszy), ale jest to tylko część obrazu, zwłaszcza gdy cały kod obiektu jest połączony w tym samym , a zwłaszcza, gdy system operacyjny obsługuje stronicowanie na żądanie kodu bezpośrednio z pliku wykonywalnego. W tym idealnym scenariuszu czas uruchamiania programów jest dosłownie nieistotny, ponieważ prawie wszystkie strony kodu będą już w pamięci i będą używane przez powłokę (i
init
wszelkie inne działające procesy w tle), nawet jeśli żądany program nie był uruchamiany od czasu rozruchu, ponieważ być może wystarczy załadować tylko jedną stronę pamięci, aby spełnić wymagania programu dotyczące czasu wykonywania.Jednak to jeszcze nie koniec. Zazwyczaj buduję i używam instalacji systemu operacyjnego NetBSD dla moich pełnych systemów programistycznych, łącząc statycznie wszystkie pliki binarne. Mimo że zajmuje to znacznie więcej miejsca na dysku (łącznie ~ 6,6 GB dla x86_64 ze wszystkim, w tym toolchain i X11 z linkami statycznymi) (zwłaszcza jeśli jedna pełna tablica symboli debugowania jest dostępna dla wszystkich innych programów ~ 2,5 GB), wynik nadal działa ogólnie szybciej, a dla niektórych zadań zużywa nawet mniej pamięci niż typowy system z dynamicznym łączem, który służy do udostępniania stron kodowych biblioteki. Dysk jest tani (nawet szybki), a pamięć do buforowania często używanych plików dyskowych jest również stosunkowo tania, ale cykle procesora tak naprawdę nie są, i ponoszenie
ld.so
kosztów uruchomienia za każdy proces, który rozpoczyna się coczas, w którym się uruchomi, zajmie wiele godzin cykli procesora od zadań wymagających uruchomienia wielu procesów, szczególnie gdy te same programy są używane w kółko, takie jak kompilatory w systemie programistycznym. Statycznie połączone programy narzędziowe mogą skracać czas kompilacji wielu architektur całego systemu operacyjnego o kilka godzin . Muszę jeszcze wbudować łańcuch narzędzi w mój pojedynczycrunchgen
plik binarny, ale podejrzewam, że kiedy to zrobię, zaoszczędzę więcej czasu na kompilacji z powodu wygranej w pamięci podręcznej procesora.źródło
Łączenie statyczne obejmuje pliki potrzebne programowi w jednym pliku wykonywalnym.
Dynamiczne łączenie jest tym, co można by uznać za zwykłe, powoduje, że plik wykonywalny, który nadal wymaga bibliotek DLL i znajduje się w tym samym katalogu (lub biblioteki DLL mogą znajdować się w folderze systemowym).
(DLL = biblioteka linków dynamicznych )
Dynamicznie połączone pliki wykonywalne są kompilowane szybciej i nie wymagają tak dużych zasobów.
źródło
Static linking
jest procesem w czasie kompilacji, gdy połączona treść jest kopiowana do podstawowego pliku binarnego i staje się pojedynczym plikiem binarnym.Cons:
Dynamic linking
to proces w czasie wykonywania, gdy ładowana jest połączona zawartość. Ta technika pozwala:ABI
stabilność [Informacje]Cons:
źródło