Czy należy unikać STL w dużych aplikacjach?

24

Może to zabrzmieć jako dziwne pytanie, ale w moim dziale mamy problemy z następującą sytuacją:

Pracujemy tutaj nad aplikacją serwerową, która staje się coraz większa, nawet w momencie, gdy rozważamy podzielenie jej na różne części (pliki DLL), dynamiczne ładowanie w razie potrzeby, a następnie rozładowywanie, aby móc obsłużyć problemy z wydajnością.

Ale: funkcje, których używamy, przekazują parametry wejściowe i wyjściowe jako obiekty STL, i jak wspomniano w odpowiedzi na przepełnienie stosu , jest to bardzo zły pomysł. (Post zawiera kilka ± rozwiązań i hacków, ale nie wszystko wygląda solidnie).

Oczywiście moglibyśmy zastąpić parametry wejściowe / wyjściowe standardowymi typami C ++ i tworzyć obiekty STL z tych, które znajdują się w funkcjach, ale może to powodować spadek wydajności.

Czy można stwierdzić, że jeśli rozważasz zbudowanie aplikacji, która może wzrosnąć tak bardzo, że jeden komputer nie będzie w stanie jej obsługiwać, nie możesz w ogóle używać STL jako technologii?

Więcej informacji na temat tego pytania:
Wydaje się, że istnieją pewne nieporozumienia dotyczące pytania: problem jest następujący:
Moja aplikacja wykorzystuje ogromną wydajność (procesor, pamięć) w celu dokończenia pracy i chciałbym podzielić tę pracę na różne części (ponieważ program jest już podzielony na wiele funkcji), tworzenie bibliotek DLL z mojej aplikacji i umieszczanie niektórych funkcji w tabeli eksportu tych bibliotek nie jest trudne. Spowodowałoby to następującą sytuację:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 to instancja aplikacji zainstalowanej na komputerze Machine1, natomiast App_Inst2 to instancja tej samej aplikacji zainstalowanej na komputerze Machine2.
DLL1.x jest biblioteką DLL zainstalowaną na komputerze Machine1, natomiast DLL2.x jest biblioteką DLL zainstalowaną na komputerze Machine2.
DLLx.1 obejmuje eksportowaną funkcję 1.
DLLx.2 obejmuje funkcję eksportu2.

Teraz na maszynie 1 chciałbym wykonać funkcję 1 i funkcję 2. Wiem, że spowoduje to przeciążenie Machine1, dlatego chciałbym wysłać wiadomość do App_Inst2, prosząc tę ​​instancję aplikacji o wykonanie funkcji 2.

Parametry wejściowe / wyjściowe funkcji1 i funkcji2 są obiektami STL (C ++ Standard Type Library) i regularnie mogę oczekiwać, że klient dokona aktualizacji App_Inst1, App_Inst2, DLLx.y (ale nie wszystkie z nich, klient może zaktualizować Machine1, ale nie Machine2, lub tylko aktualizuj aplikacje, ale nie biblioteki DLL lub odwrotnie, ...). Oczywiście, jeśli interfejs (parametry wejściowe / wyjściowe) ulegnie zmianie, wówczas klient jest zmuszony dokonać kompletnych aktualizacji.

Jednak, jak wspomniano w odnośnym adresie URL StackOverflow, prosta ponowna kompilacja App_Inst1 lub jednej z bibliotek DLL może spowodować rozpad całego systemu, stąd mój oryginalny tytuł tego postu, odradzający użycie STL (Standardowy szablon C ++ Biblioteka) dla dużych aplikacji.

Mam nadzieję, że niniejszym wyjaśniłem niektóre pytania / wątpliwości.

Dominique
źródło
44
Czy na pewno masz problemy z wydajnością z powodu rozmiaru pliku wykonywalnego ? Czy możesz dodać kilka szczegółów na temat tego, czy realistyczne jest założenie, że całe oprogramowanie jest skompilowane przy użyciu tego samego kompilatora (na przykład za jednym razem na serwerze kompilacji), czy też naprawdę chcesz podzielić się na niezależne zespoły?
nvoigt
5
Zasadniczo potrzebujesz osoby, której dedykowanym zadaniem jest „build manager” i „release manager”, aby upewnić się, że wszystkie projekty C ++ są kompilowane w tej samej wersji kompilatora i przy identycznych ustawieniach kompilatora C ++, skompilowanych ze spójnej migawki (wersji) źródła kod itp. Zazwyczaj odbywa się to pod hasłem „ciągłej integracji”. Jeśli szukasz w Internecie, znajdziesz wiele artykułów i narzędzi. Przestarzałe praktyki mogą się wzmocnić - jedna przestarzała praktyka może prowadzić do przedawnienia wszystkich praktyk.
rwong
8
Akceptowana odpowiedź w połączonym pytaniu stwierdza, że ​​problem dotyczy ogólnie wywołań C ++. Więc „C ++, ale nie STL” nie pomaga, musisz iść z pustym C, aby być po bezpiecznej stronie (ale także zobacz odpowiedzi, serializacja jest prawdopodobnie lepszym rozwiązaniem).
Frax
52
dynamiczne ładowanie w razie potrzeby, a następnie rozładowywanie, aby móc poradzić sobie z problemami z wydajnością Jakie „problemy z wydajnością”? Nie znam żadnych problemów poza używaniem zbyt dużej ilości pamięci, którą można naprawić, usuwając rzeczy takie jak biblioteki DLL z pamięci - a jeśli to jest problem, najłatwiej jest po prostu kupić więcej pamięci RAM. Czy profilowałeś aplikację, aby zidentyfikować rzeczywiste wąskie gardła wydajności? Ponieważ brzmi to jak problem XY - masz nieokreślone „problemy z wydajnością” i ktoś już zdecydował się na rozwiązanie.
Andrew Henle,
4
@MaxBarraclough „STL” jest doskonale akceptowany jako alternatywna nazwa kontenerów szablonowych i funkcji, które zostały włączone do biblioteki standardowej C ++. W rzeczywistości podstawowe wytyczne C ++, napisane przez Bjarne Stroustrup i Herb Suttera, wielokrotnie wspominają o „STL”, kiedy o nich mowa. Nie można uzyskać o wiele bardziej wiarygodnego źródła niż to.
Sean Burton

Odpowiedzi:

110

Jest to klasyczny problem XY.

Twoim prawdziwym problemem są problemy z wydajnością. Jednak twoje pytanie wyjaśnia, że ​​nie przeprowadziłeś profilowania ani innych ocen tego, skąd faktycznie pochodzą problemy z wydajnością. Zamiast tego masz nadzieję, że podzielenie kodu na biblioteki DLL magicznie rozwiąże problem (czego nie zrobi, dla przypomnienia), a teraz martwisz się o jeden aspekt tego nierozwiązania.

Zamiast tego musisz rozwiązać prawdziwy problem. Jeśli masz wiele plików wykonywalnych, sprawdź, który z nich powoduje spowolnienie. W tym momencie upewnij się, że program zajmuje cały czas przetwarzania, a nie źle skonfigurowany sterownik Ethernet lub coś w tym rodzaju. Następnie rozpocznij profilowanie różnych zadań w kodzie. Precyzyjny zegar jest tutaj Twoim przyjacielem. Klasycznym rozwiązaniem jest monitorowanie średniego i najgorszego czasu przetwarzania dla części kodu.

Gdy masz dane, możesz dowiedzieć się, jak poradzić sobie z problemem, a następnie ustalić, gdzie je zoptymalizować.

Graham
źródło
54
„Zamiast tego masz nadzieję, że podzielenie kodu na biblioteki DLL magicznie rozwiąże problem (czego nie zrobi, dla przypomnienia)” - +1 za to. Twój system operacyjny prawie na pewno implementuje stronicowanie popytu, które osiąga dokładnie taki sam wynik jak funkcja ładowania i rozładowywania w bibliotekach DLL, tylko automatycznie, zamiast wymagać ręcznej interwencji. Nawet jeśli lepiej przewidzisz, jak długo fragment kodu powinien się zawiesić, gdy jest używany, niż system pamięci wirtualnej systemu operacyjnego (co jest w rzeczywistości mało prawdopodobne), system operacyjny buforuje plik DLL i neguje twoje wysiłki .
Jules
@Jules Zobacz aktualizację - wyjaśnili, że biblioteki DLL istnieją tylko na osobnych komputerach, więc może widzę, że to rozwiązanie działa. Ale teraz jest narzut komunikacyjny, więc trudno być pewnym.
Izkata
2
@Izkata - wciąż nie jest do końca jasne, ale myślę, że opisano, że chcą dynamicznie wybierać (na podstawie konfiguracji środowiska wykonawczego) wersję każdej funkcji, która jest lokalna lub zdalna. Ale żadna część pliku EXE, która nigdy nie jest używana na danym komputerze, po prostu nigdy nie zostanie załadowana do pamięci, więc użycie bibliotek DLL do tego celu nie jest konieczne. Wystarczy dołączyć obie wersje wszystkich funkcji do standardowej kompilacji i utworzyć tabelę wskaźników funkcji (lub obiektów na żądanie C ++ lub dowolnej innej metody), aby wywołać odpowiednią wersję każdej funkcji.
Jules
38

Jeśli musisz podzielić oprogramowanie na wiele fizycznych komputerów, musisz mieć jakąś formę serializacji podczas przesyłania danych między komputerami, ponieważ tylko w niektórych przypadkach możesz po prostu wysłać dokładnie ten sam plik binarny między komputerami. Większość metod serializacji nie ma problemów z obsługą typów STL, więc sprawa nie jest czymś, co by mnie martwiło.

Jeśli musisz podzielić aplikację na biblioteki współdzielone (DLL) (zanim zrobisz to ze względu na wydajność, naprawdę powinieneś upewnić się, że rzeczywiście rozwiąże to problemy z wydajnością) przekazywanie obiektów STL może być problemem, ale nie musi tak być. Jak już podałeś łącze, przekazywanie obiektów STL działa, jeśli używasz tego samego kompilatora i tych samych ustawień kompilatora. Jeśli użytkownicy dostarczą biblioteki DLL, możesz nie być w stanie łatwo na to liczyć. Jeśli podasz wszystkie biblioteki DLL i skompilujesz wszystko razem, być może będziesz w stanie na to liczyć, a używanie obiektów STL ponad granicami bibliotek DLL stanie się bardzo możliwe. Nadal musisz uważać na ustawienia kompilatora, aby nie uzyskać wielu różnych hałd, jeśli przejdziesz na własność obiektu, chociaż nie jest to problem specyficzny dla STL.

Pierre Andersson
źródło
1
Tak, a zwłaszcza część dotycząca przekazywania przydzielonych obiektów przez granice DLL / so. Ogólnie rzecz biorąc, jedynym sposobem na całkowite uniknięcie problemu z wieloma alokatorami jest upewnienie się, że DLL / so (lub biblioteka!), Która alokowała strukturę, również ją zwalnia. Dlatego widzisz wiele interfejsów API w stylu C napisanych w ten sposób: jawny bezpłatny interfejs API dla każdego interfejsu API, który przekazuje przydzieloną tablicę / strukturę. Dodatkowym problemem związanym z STL jest to, że osoba dzwoniąca może oczekiwać modyfikacji zmodyfikowanej złożonej struktury danych (dodawanie / usuwanie elementów), a to też nie jest dozwolone. Ale trudno jest to wyegzekwować.
davidbak
1
Gdybym musiał podzielić taką aplikację, prawdopodobnie użyłbym COM, ale generalnie zwiększa to rozmiar kodu, ponieważ każdy komponent przynosi własne biblioteki C i C ++ (które mogą być udostępniane, gdy są takie same, ale mogą się różnić w razie potrzeby, np. podczas przejść. Nie jestem jednak przekonany, że jest to właściwy sposób postępowania w przypadku problemu PO
Simon Richter
2
Jako konkretny przykład, jest wysoce prawdopodobne, że program gdzieś chce wysłać tekst na inną maszynę. W pewnym momencie pojawi się wskaźnik do niektórych znaków zaangażowanych w reprezentowanie tego tekstu. Absolutnie nie można po prostu przesłać fragmentów tych wskaźników i oczekiwać określonego zachowania po stronie odbierającej
Caleth
20

Pracujemy tutaj nad aplikacją serwerową, która staje się coraz większa, nawet w momencie, gdy rozważamy podzielenie jej na różne części (biblioteki DLL), dynamiczne ładowanie w razie potrzeby, a następnie rozładowywanie, aby móc obsłużyć problemy z wydajnością

Pamięć RAM jest tania, dlatego nieaktywny kod jest tani. Ładowanie i rozładowywanie kodu (szczególnie rozładowywanie) jest delikatnym procesem i jest mało prawdopodobne, aby miało znaczący wpływ na wydajność twoich programów na nowoczesnym sprzęcie stacjonarnym / serwerowym.

Pamięć podręczna jest droższa, ale wpływa tylko na kod, który jest ostatnio aktywny, a nie na kod, który jest nieużywany w pamięci.

Ogólnie rzecz biorąc, programy przerastają komputery z powodu rozmiaru danych lub czasu procesora, a nie rozmiaru kodu. Jeśli rozmiar twojego kodu staje się tak duży, że powoduje poważne problemy, prawdopodobnie prawdopodobnie zastanawiasz się, dlaczego tak się dzieje.

Ale: funkcje, których używamy, przekazują parametry wejściowe i wyjściowe jako obiekty STL, i jak wspomniano w tym adresie URL StackOverflow, jest to bardzo zły pomysł.

Powinno być w porządku, o ile biblioteki DLL i pliki wykonywalne są zbudowane z tego samego kompilatora i dynamicznie połączone z tą samą biblioteką środowiska wykonawczego C ++. Wynika z tego, że jeśli aplikacja i powiązane z nią biblioteki DLL zostaną zbudowane i wdrożone jako pojedyncza jednostka, nie powinno to stanowić problemu.

Problemem może być budowanie bibliotek przez różne osoby lub ich osobna aktualizacja.

Czy można stwierdzić, że jeśli zastanawiasz się nad stworzeniem aplikacji, która może urosnąć tak bardzo, że jeden komputer nie będzie w stanie jej obsługiwać, nie możesz w ogóle używać STL jako technologii?

Nie całkiem.

Po rozpoczęciu rozprzestrzeniania aplikacji na wielu komputerach masz cały szereg rozważań dotyczących sposobu przekazywania danych między tymi komputerami. Szczegóły dotyczące tego, czy stosowane są typy STL, czy bardziej podstawowe typy, mogą zostać utracone w hałasie.

Peter Green
źródło
2
Kod nieaktywny prawdopodobnie nigdy nie zostanie załadowany do pamięci RAM. Większość systemów operacyjnych ładuje strony z plików wykonywalnych tylko wtedy, gdy są one faktycznie wymagane.
Jules
1
@Jules: Jeśli martwy kod zostanie zmieszany z aktywnym kodem (z wielkością strony = ziarnistość 4k), zostanie zmapowany + załadowany. Pamięć podręczna działa na znacznie drobniejszej ziarnistości (64B), więc nadal jest w większości prawdą, że nieużywane funkcje nie zaszkodzą zbytnio. Każda strona wymaga jednak wpisu TLB i (w przeciwieństwie do pamięci RAM), która jest rzadkim zasobem środowiska wykonawczego. (Mapowania oparte na plikach zazwyczaj nie używają stron ukrytych, przynajmniej nie w systemie Linux; Jedna strona to 2 MB na x86-64, więc można pokryć znacznie więcej kodu lub danych bez utraty błędów TLB przy użyciu stron tytułowych.)
Peter Cordes
1
Co zauważa @PeterCordes: Pamiętaj, aby używać „PGO” jako części procesu kompilacji do wydania!
JDługosz
13

Nie, nie sądzę, aby taki wniosek był następujący. Nawet jeśli twój program jest dystrybuowany na wielu komputerach, nie ma powodu, aby użycie STL wewnętrznie zmusiło cię do użycia go w komunikacji między modułami / procesami.

W rzeczywistości uważam, że od samego początku należy oddzielić projektowanie zewnętrznych interfejsów od implementacji wewnętrznej, ponieważ te pierwsze będą bardziej solidne / trudne do zmiany w porównaniu do tego, co jest używane wewnętrznie

Bwmat
źródło
7

Nie rozumiesz sedna tego pytania.

Istnieją dwa typy bibliotek DLL. Twój własny i cudzego. „Problem STL” polega na tym, że ty i oni nie korzystacie z tego samego kompilatora. Oczywiście nie stanowi to problemu dla własnej biblioteki DLL.

MSalters
źródło
5

Jeśli skompilujesz biblioteki DLL z tego samego drzewa źródłowego w tym samym czasie za pomocą tego samego kompilatora i opcji kompilacji, to będzie działać OK.

Jednak „podzielony na Windows” sposób dzielenia aplikacji na wiele części, z których niektóre nadają się do ponownego użycia, to komponenty COM . Mogą być małe (pojedyncze elementy sterujące lub kodeki) lub duże (IE jest dostępny jako element sterujący COM w pliku mshtml.dll).

dynamiczne ładowanie w razie potrzeby, a następnie rozładowywanie

W przypadku aplikacji serwerowej prawdopodobnie będzie to miało straszną wydajność; jest to naprawdę wykonalne tylko wtedy, gdy masz aplikację, która przechodzi przez wiele faz w długim okresie czasu, dzięki czemu wiesz, kiedy coś już nie będzie potrzebne. Przypomina mi gry DOS z mechanizmem nakładki.

Poza tym, jeśli twój system pamięci wirtualnej działa poprawnie, poradzi sobie z tym, stronicując nieużywane strony kodowe.

może urosnąć tak bardzo, że jeden komputer nie będzie już w stanie tego znieść

Kup większy komputer.

Nie zapominaj, że przy odpowiedniej optymalizacji laptop może przewyższyć klaster hadoop.

Jeśli naprawdę potrzebujesz wielu systemów, musisz bardzo dokładnie przemyśleć granicę między nimi, ponieważ tam właśnie są koszty serializacji. W tym miejscu powinieneś zacząć szukać frameworków takich jak MPI.

pjc50
źródło
1
„jest to naprawdę opłacalne tylko wtedy, gdy masz aplikację, która przechodzi przez wiele faz przez długi okres czasu, dzięki czemu wiesz, kiedy coś już nie będzie potrzebne” - nawet wtedy mało prawdopodobne jest, aby pomóc, ponieważ system operacyjny buforuj pliki DLL, co prawdopodobnie zajmie więcej pamięci niż tylko włączenie funkcji bezpośrednio do podstawowego pliku wykonywalnego. Nakładki są użyteczne tylko w systemach bez wirtualnej pamięci lub gdy wirtualna przestrzeń adresowa jest czynnikiem ograniczającym (zakładam, że ta aplikacja jest 64-bitowa, a nie 32 ...).
Jules
3
„Kup większy komputer” +1. Możesz teraz nabywać systemy z wieloma terabajtami pamięci RAM. Możesz wynająć jeden w Amazon za mniej niż stawkę godzinową jednego dewelopera. Ile czasu programisty zamierzasz poświęcić na optymalizację kodu w celu zmniejszenia zużycia pamięci?
Jules
2
Największy problem, z jakim się spotkałem przy „kupowaniu większego komputera”, związany był z pytaniem „jak daleko Twoja aplikacja się skaluje?”. Moja odpowiedź brzmiała: „ile jesteś gotów wydać na test? Ponieważ spodziewam się, że zwiększy się on do tej pory, że wynajęcie odpowiedniej maszyny i przygotowanie odpowiednio dużego testu będzie kosztować tysiące dolarów. Żaden z naszych klientów nie jest nawet blisko do tego, co może zrobić komputer jednoprocesorowy. ”. Wielu starszych programistów nie ma realistycznego pojęcia, ile komputerów dorosło; sama karta wideo w nowoczesnych komputerach to superkomputer według standardów XX wieku.
MSalters
Komponenty COM? Może w latach 90., ale teraz?
Peter Mortensen
@MSalters - racja ... każdy, kto ma jakiekolwiek pytania o to, jak daleko można skalować aplikację na jednym komputerze, powinien zapoznać się ze specyfikacją typu instancji Amazon EC2 x1e.32xlarge - łącznie 72 fizyczne rdzenie procesorów w maszynie, zapewniając 128 wirtualnych rdzeni na 2,3 GHz (z możliwością podziału do 3,1 GHz), potencjalnie nawet przepustowość pamięci 340 GB / s (w zależności od rodzaju zainstalowanej pamięci, co nie jest opisane w specyfikacji) i 3,9 TB pamięci RAM. Ma wystarczającą pamięć podręczną, aby uruchomić większość aplikacji bez dotykania głównej pamięci RAM. Nawet bez procesora graficznego jest tak potężny jak 500-węzłowy klaster superkomputerów od 2000 roku.
Jules
0

Pracujemy tutaj nad aplikacją serwerową, która staje się coraz większa, nawet w momencie, gdy rozważamy podzielenie jej na różne części (pliki DLL), dynamiczne ładowanie w razie potrzeby, a następnie rozładowywanie, aby móc obsłużyć problemy z wydajnością.

Pierwsza część ma sens (dzielenie aplikacji na różne maszyny, ze względu na wydajność).

Druga część (ładowanie i rozładowywanie bibliotek) nie ma sensu, ponieważ jest to dodatkowy wysiłek i nie poprawi (naprawdę) rzeczy.

Opisany problem lepiej rozwiązać za pomocą dedykowanych maszyn obliczeniowych, ale nie powinny one działać z tą samą (główną) aplikacją.

Klasyczne rozwiązanie wygląda następująco:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Pomiędzy front-endem a maszynami obliczeniowymi możesz mieć dodatkowe rzeczy, takie jak usługi równoważenia obciążenia i monitorowanie wydajności, a wyspecjalizowane przetwarzanie keeing na dedykowanych maszynach jest dobre do buforowania i optymalizacji przepustowości.

Nie oznacza to w żaden sposób dodatkowego ładowania / wyładowywania bibliotek DLL ani żadnego związku z STL.

Oznacza to, że używaj STL wewnętrznie zgodnie z wymaganiami i szereguj swoje dane między elementami (szereguj bufory protokołu i protokołu oraz rodzaje problemów, które rozwiązują).

To powiedziawszy, z ograniczonymi informacjami, które podałeś, wygląda to jak klasyczny problem xy (jak powiedział @Graham).

utnapistim
źródło