Jak poradzić sobie z problemem (kompilacji) dużej bazy kodu?

10

Chociaż potrafię kodować, nie mam jeszcze doświadczenia w pracy przy dużych projektach. Do tej pory robiłem albo kodowanie małych programów, które kompilowałem w ciągu kilku sekund (różne ćwiczenia c / c ++, takie jak algorytmy, zasady programowania, pomysły, paradygmaty, lub po prostu wypróbowywanie interfejsu API ...) lub praca nad kilkoma mniejszymi projektami, które były wykonane w języku (językach) skryptowym (python, php, js), w których nie jest wymagana kompilacja.

Chodzi o to, że kiedy koduję w języku skryptowym, za każdym razem, gdy chcę spróbować, czy coś działa - po prostu uruchamiam skrypt i sprawdzam, co się stanie. Jeśli coś nie działa, mogę po prostu zmienić kod i wypróbować go ponownie, uruchamiając skrypt ponownie i robiąc to, dopóki nie uzyskam oczekiwanego rezultatu. Chodzi mi o to, że nie musisz czekać na wszystko do skompilowania, dzięki czemu łatwo jest wziąć dużą bazę kodu, zmodyfikować ją, dodać do niej coś lub po prostu grać z nią - zmiany można zobaczyć natychmiast.

Jako przykład wezmę Wordpress. Łatwo jest spróbować dowiedzieć się, jak utworzyć dla niego wtyczkę. Najpierw zaczniesz od utworzenia prostej wtyczki „Hello World”, następnie utworzysz prosty interfejs dla panelu administracyjnego, aby zapoznać się z interfejsem API, a następnie zbudujesz go i stworzysz coś bardziej złożonego, w międzyczasie zmieniając jego wygląd razy .. Pomysł powtarzania kompilacji czegoś tak dużego jak WP w kółko, po każdej drobnej zmianie, aby spróbować „jeśli to działa” i „jak to działa / czuje się” wydaje się po prostu nieefektywny, powolny i niewłaściwy.

Jak mogę to zrobić z projektem napisanym w skompilowanym języku? Chciałbym uczestniczyć w niektórych projektach typu open source, a to pytanie wciąż mnie denerwuje. Sytuacja prawdopodobnie różni się w zależności od projektu, w którym niektóre z nich, które zostały mądrze przemyślane, będą w pewnym sensie „modułowe”, podczas gdy inne będą tylko jednym wielkim blobem, który należy ponownie kompilować.

Chciałbym dowiedzieć się więcej o tym, jak to zrobić poprawnie. Jakie są typowe praktyki, podejścia i projekty projektów (wzorce?), Aby sobie z tym poradzić? Jak nazywa się ta „modułowość” w świecie programistów i po co powinienem szukać w Google, aby dowiedzieć się więcej na ten temat? Czy często projekty wyrastają z pierwotnych proporcji, które po pewnym czasie stają się kłopotliwe? Czy jest jakiś sposób na uniknięcie długiego kompilowania niezbyt dobrze zaprojektowanych projektów? Sposób na ich modularyzację (może z wyłączeniem nieistotnych części programu podczas opracowywania (jakieś inne pomysły?))?

Dzięki.

pootzko
źródło
4
Ob. XKCD i odpowiednia koszulka myślowa * 8 ')
Mark Booth
1
Jeśli pracujesz nad wystarczająco dużym projektem o wystarczająco dużym budżecie, możesz zmusić serwery kompilacji do wykonania kompilacji za Ciebie :)
SoylentGray
@Chad - Wiem o tym, ale w tej chwili to tylko moja domowa maszyna stacjonarna
GNU
@Chad Ok, więc mówisz nam, że potrzebujemy dedykowanych serwerów, aby poradzić sobie z masą Java (lub innym skompilowanym językiem)? To totalne badziewie
Kanion Kolob
1
@KolobCanyon - Nie, mówię, że istnieje skala, w której możesz pracować, która wymagałaby ich. i że są teraz wystarczająco tanie, że posiadanie maszyny wirtualnej na żądanie poświęconej szybkiej kompilacji i automatyzacji testów jest wystarczająco łatwe, aby skala nie była tak duża.
SoylentGray

Odpowiedzi:

8

Tak jak powiedziano, nigdy nie rekompilujesz całego projektu za każdym razem, gdy dokonasz niewielkiej zmiany. Zamiast tego rekompilujesz tylko część kodu, która uległa zmianie, a także cały kod w zależności od niego.

W C / C ++ kompilacja jest dość prosta. Ci skompilować tłumaczyć każdy plik źródłowy do kodu maszynowego (nazywamy je sprzeciw pliki * .o), a następnie połączyć wszystkie pliki obiektów w jeden duży plik wykonywalny.

Tak jak wspomniano w MainMa, niektóre biblioteki są wbudowane w osobne pliki, które będą dynamicznie łączone w czasie wykonywania z plikiem wykonywalnym. Biblioteki te nazywane są obiektami współdzielonymi (* .so) w systemach Unix i dynamicznie połączonych bibliotekach (DLL) w systemie Windows. Biblioteki dynamiczne mają wiele zalet, z których jedną jest to, że nie trzeba ich kompilować / łączyć, chyba że kod źródłowy skutecznie się zmieni.

Istnieją narzędzia automatyzacji kompilacji, które pomogą Ci:

  • Określ zależności między różnymi częściami drzewa źródłowego.
  • Uruchamiaj punktualne, dyskretne kompilacje tylko w zmodyfikowanej części.

Najsłynniejsze (make, ant, maven, ...) mogą automatycznie wykryć, które części kodu zostały zmienione od czasu ostatniej kompilacji i dokładnie jaki obiekt / plik binarny wymaga aktualizacji.

Jest to jednak (stosunkowo niewielki) koszt napisania „skryptu kompilacji”. Jest to plik zawierający wszystkie informacje o twojej kompilacji, takie jak definiowanie celów i ich zależności, definiowanie jakiego kompilatora chcesz i jakich opcji użyć, definiowanie środowiska kompilacji, ścieżek bibliotek, ... Być może słyszałeś o Makefiles (bardzo powszechne w świecie Unix) lub build.xml (bardzo popularny w świecie Java). Tak robią.

rahmu
źródło
2
Ant (Java) nie jest w stanie określić, co wymaga ponownej kompilacji. Obsługuje trywialną część zadania, kompiluje zmieniony kod źródłowy, ale w ogóle nie rozumie zależności klasowych. W tym celu polegamy na IDE, które popełniają błąd, jeśli podpis metody zostanie zmieniony w sposób, który nie wymaga zmiany kodu wywołującego.
kevin cline,
@kevincline I sekundę po tym - ANT kompiluje wszystko, chyba że określisz coś innego w build.xmlpliku
Kolob Canyon
7

Nie kompilujesz całego projektu za każdym razem. Na przykład, jeśli jest to aplikacja C / C ++, istnieje szansa, że ​​zostanie ona podzielona na biblioteki (biblioteki DLL w systemie Windows), a każda biblioteka zostanie skompilowana osobno.

Sam projekt jest zazwyczaj kompilowany codziennie na dedykowanym serwerze: są to kompilacje nocne. Proces ten może zająć dużo czasu, ponieważ obejmował nie tylko czas kompilacji, ale także czas poświęcony na przeprowadzanie testów jednostkowych, innych testów i innych procesów.

Arseni Mourzenko
źródło
3
Jeśli nie zrekompiluję tego wszystkiego, to kiedy będę miał czas na zabawę z moim Trebuchetem
SoylentGray 27.09.11
5

Sądzę, że wszystkie dotychczasowe odpowiedzi wskazywały na to, że duże projekty oprogramowania prawie zawsze dzielą się na znacznie mniejsze części. Każdy kawałek jest zwykle przechowywany we własnym pliku.

Te elementy są indywidualnie kompilowane w celu tworzenia obiektów. Obiekty są następnie łączone ze sobą, tworząc produkt końcowy. [W pewnym sensie przypomina to budowanie z Legos. Nie próbujesz uformować ostatecznej rzeczy z jednego dużego kawałka plastiku, zamiast tego łączysz kilka mniejszych kawałków.]

Podział projektu na osobne kompilacje pozwala na pewne fajne rzeczy.

Budynek przyrostowy

Po pierwsze, po zmianie jednego elementu zwykle nie trzeba ponownie kompilować wszystkich elementów. Ogólnie rzecz biorąc, dopóki nie zmienisz sposobu, w jaki inne elementy oddziałują z twoim kawałkiem, inne nie muszą być ponownie kompilowane.

Daje to początek pomysłowi przyrostowego budowania . Podczas wykonywania kompilacji przyrostowej rekompilowane są tylko te części, na które zmiana miała wpływ. To znacznie przyspiesza czas programowania. To prawda, że ​​nadal możesz poczekać, aż wszystko zostanie ponownie połączone, ale wciąż jest to oszczędność w porównaniu z koniecznością ponownej kompilacji i ponownego połączenia. (BTW: Niektóre systemy / języki obsługują łączenie przyrostowe, więc tylko te rzeczy, które uległy zmianie, muszą zostać ponownie połączone. Kosztem tego jest zwykle słaba wydajność i rozmiar kodu.)

Testów jednostkowych

Drugą rzeczą, którą pozwalają ci małe kawałki, jest indywidualne testowanie kawałków przed ich połączeniem. Jest to znane jako testowanie jednostkowe . W testach jednostkowych każda jednostka jest indywidualnie testowana, zanim zostanie zintegrowana (połączona) z resztą systemu. Testy jednostkowe są zwykle pisane, aby można je było szybko uruchomić bez angażowania reszty systemu.

Ograniczający przypadek zastosowania testów jest widoczny w Test Driven Development (TDD). W tym modelu programistycznym żaden kod nie jest zapisywany / modyfikowany, chyba że ma on na celu naprawienie nieudanego testu.

Ułatwienie

Rozbijanie rzeczy wydaje się dobre, ale wydaje się również, że potrzeba dużo pracy, aby zbudować projekt: musisz dowiedzieć się, jakie elementy się zmieniły i co zależy od tych elementów, skompiluj każdy element, a następnie połącz wszystko ze sobą.

Na szczęście programiści są leniwi *, więc wymyślają wiele narzędzi, aby ułatwić sobie pracę. W tym celu napisano wiele narzędzi automatyzujących powyższe zadanie. Najsłynniejsze z nich zostały już wspomniane (marka, mrówka, maven). Narzędzia te pozwalają określić, jakie elementy należy złożyć, aby wykonać końcowy projekt, i jak elementy zależą od siebie (tj. Jeśli to zmienisz, należy to ponownie skompilować). Rezultat jest taki, że wydanie tylko jednego polecenia pozwala ustalić, co należy ponownie skompilować, skompilować i ponownie połączyć wszystko.

Ale to wciąż pozwala ustalić, jak rzeczy mają się do siebie. To dużo pracy i jak powiedziałem wcześniej, programiści są leniwi. Więc wymyślili inną klasę narzędzi. Te narzędzia zostały napisane w celu określenia zależności dla Ciebie! Często narzędzia te są częścią zintegrowanych środowisk programistycznych (IDE), takich jak Eclipse i Visual Studio, ale są też pewne samodzielne narzędzia używane zarówno do ogólnych, jak i specyficznych aplikacji (makedep, QMake dla programów Qt).

* W rzeczywistości programiści nie są zbyt leniwi, po prostu lubią spędzać czas na rozwiązywaniu problemów, nie wykonując powtarzalnych zadań, które mogą być zautomatyzowane przez program.

jwernerny
źródło
5

Oto moja lista rzeczy, które możesz spróbować przyspieszyć kompilacje C / C ++:

  • Czy chcesz odbudować tylko to, co się zmieniło? Większość środowisk robi to domyślnie. Nie ma potrzeby ponownej kompilacji pliku, jeśli nie zmienił się żaden z nagłówków. Podobnie nie ma powodu, aby przebudowywać dll / exe, jeśli wszystkie łącza w objs / lib nie uległy zmianie.
  • Umieść elementy innych firm, które nigdy się nie zmieniają, oraz powiązane nagłówki w obszarze biblioteki kodów tylko do odczytu. Potrzebujesz tylko nagłówków i powiązanych plików binarnych. Nigdy nie powinieneś potrzebować odbudowywać tego ze źródła innego niż może raz.
  • Podczas odbudowy wszystkiego dwoma ograniczającymi doświadczeniami były liczba rdzeni i szybkość dysku . Zdobądź mocną czterordzeniową, hiperwątkową maszynę z naprawdę dobrym dyskiem twardym, a Twoja wydajność wzrośnie. Rozważ dysk półprzewodnikowy - pamiętaj, że tanie mogą być gorsze niż dobry dysk twardy. Rozważ użycie raidu, aby zwiększyć dysk twardy
  • Użyj rozproszonego systemu kompilacji, takiego jak Incredibuild, który podzieli kompilację na inne stacje robocze w sieci. (Upewnij się, że masz solidną sieć).
  • Skonfiguruj kompilację jedności, aby uniknąć ciągłego ponownego ładowania plików nagłówkowych.
Doug T.
źródło
Z mojego doświadczenia (niewiele, ale dobrze) prędkość dysku zaczyna być nieistotna, jeśli twój projekt wykracza poza „bardzo mały”. Pomyśl tylko o tym, co powiesz w następnym punkcie: używasz sieci do przyspieszenia kompilacji. Jeśli dysk był dużym wąskim gardłem, uciekanie się do sieci nie wydaje się zbyt dobrym posunięciem.
R. Martinho Fernandes
Innym tanim rozwiązaniem jest kompilacja w tmpfs. Może znacznie zwiększyć wydajność, jeśli proces kompilacji jest związany z operacjami we / wy.
Artefact2
4

Pomysł ciągłej rekompilacji czegoś tak dużego jak WP w kółko, po każdej drobnej zmianie, aby spróbować „jeśli to działa” i „jak to działa / czuje się” wydaje się po prostu nieefektywny, powolny i niewłaściwy.

Wykonanie czegoś interpretowanego jest również bardzo nieefektywne i powolne oraz (prawdopodobnie) nieprawidłowe. Narzekasz na wymagania czasowe na komputerze programisty, ale brak kompilacji powoduje wymagania czasowe na komputerze użytkownika , co jest prawdopodobnie znacznie gorsze.

Co ważniejsze, nowoczesne systemy mogą przeprowadzać dość zaawansowane przyrostowe przebudowy i nie jest powszechne rekompilowanie całości w przypadku drobnych zmian - systemy kompilowane mogą zawierać komponenty skryptu, szczególnie wspólne dla takich rzeczy jak interfejs użytkownika.

DeadMG
źródło
1
Uważam, że moje pytanie nie miało być interpretowane vs. kompilacja debaty o podejściu. Zamiast tego poprosiłem o radę, jak prawidłowo opracować duży (skompilowany) projekt. Dzięki za pomysł przyrostowej przebudowy.
pootzko
@pootzko: Cóż, niesprawiedliwe jest dyskutowanie na temat wad kompilacji, kiedy nie mówimy również o wadach tłumaczenia.
DeadMG
1
nie, nie jest. to kolejna debata i nie ma nic wspólnego z moim pytaniem. Nie twierdzę, że nie należy o tym dyskutować. powinno, ale nie tutaj.
pootzko
@pootzko: Zatem nie powinieneś poświęcać większości swojego pytania na wyliczanie tego, czego nie lubisz w kompilowaniu. Powinieneś napisać coś znacznie krótszego i bardziej pomocnego, na przykład: „Jak można skrócić czas kompilacji dużych projektów?”.
DeadMG,
Nie wiedziałem, że muszę zapytać kogoś, w jaki sposób „powinienem” zadać pytanie…? : OI napisałem to tak, jak ja, aby lepiej wyjaśnić mój punkt widzenia, aby inni mogli go lepiej zrozumieć i wyjaśnić mi, jak osiągnąć to samo / podobne dzięki skompilowanym językom. Ponownie - nie - poprosiłem nikogo, aby powiedział mi, czy tłumaczone języki powodują gorsze wymagania czasowe na komputerze użytkownika. Wiem o tym i nie ma to nic wspólnego z moim pytaniem: „jak to się robi z językami kompilowanymi”, przepraszam. Wydaje się, że inni ludzie zorientowali się, o co pytam, więc nie sądzę, aby moje pytanie było wystarczająco jasne ...
pootzko
4
  • Częściowa przebudowa

Jeśli projekt implementuje poprawną zależność kompilacji DAG, można uniknąć rekompilacji tylko plików obiektowych, na które wpływa zmiana.

  • Wielokrotny proces kompilacji

Zakładając również, że DAG jest zależny od kompilacji, możesz kompilować używając wielu procesów. Jedno zadanie na rdzeń / procesor jest normą.

  • Testy wykonywalne

Możesz utworzyć wiele plików wykonywalnych do testowania, które łączą tylko określone pliki obiektów.

dietbuddha
źródło
2

Oprócz odpowiedzi MainMa właśnie zaktualizowaliśmy maszyny, na których pracujemy. Jednym z najlepszych zakupów, jakie zrobiliśmy, był dysk SSD, na który nie można pomóc, ale zrekompilować cały projekt.

Inną sugestią byłoby wypróbowanie innego kompilatora. Wcześniej przeszliśmy z kompilatora Javy na Jikes, a teraz przeszliśmy do korzystania z kompilatora dołączonego do Eclipse (nie wiem, czy ma nazwę), który lepiej wykorzystuje procesory wielordzeniowe.

Nasz projekt 37 000 plików skompilował się od zera przed wprowadzeniem tych zmian. Po zmianach został skrócony do 2-3 minut.

Oczywiście warto jeszcze raz wspomnieć o MainMa. Nie kompiluj całego projektu za każdym razem, gdy chcesz zobaczyć zmianę.

RP.
źródło