OK, definiujesz problem tak, aby wydawało się, że nie ma zbyt wiele miejsca na ulepszenia. Z mojego doświadczenia jest to dość rzadkie. Próbowałem to wyjaśnić w artykule dr Dobbsa w listopadzie 1993 r., Rozpoczynając od konwencjonalnie dobrze zaprojektowanego nietrywialnego programu bez oczywistych strat i przeprowadzając go przez serię optymalizacji, aż jego czas naścienny został skrócony z 48 sekund do 1,1 sekundy, a rozmiar kodu źródłowego został zmniejszony czterokrotnie. Moje narzędzie diagnostyczne było takie . Sekwencja zmian była następująca:
Pierwszym znalezionym problemem było użycie klastrów list (obecnie nazywanych „iteratorami” i „klasami kontenerów”), które stanowią ponad połowę czasu. Zostały one zastąpione dość prostym kodem, co skróciło czas do 20 sekund.
Teraz największym pochłaniaczem czasu jest tworzenie większej liczby list. Procentowo nie był wcześniej tak duży, ale teraz jest tak, ponieważ większy problem został usunięty. Znajduję sposób, aby go przyspieszyć, a czas spada do 17 sekund.
Teraz trudniej jest znaleźć oczywistych winowajców, ale jest kilka mniejszych, z którymi mogę coś zrobić, a czas spada do 13 sekund.
Teraz chyba uderzyłem w ścianę. Próbki mówią mi dokładnie, co robi, ale nie mogę znaleźć niczego, co mógłbym poprawić. Następnie zastanawiam się nad podstawową konstrukcją programu, nad jego strukturą opartą na transakcjach i pytam, czy całe przeszukiwanie listy, które wykonuje, jest faktycznie wymagane przez wymagania problemu.
Następnie natknąłem się na przeprojektowanie, w którym kod programu jest generowany (za pomocą makr preprocesora) z mniejszego zestawu źródeł, i w którym program nie stale odkrywa rzeczy, o których programista wie, że są dość przewidywalne. Innymi słowy, nie „interpretuj” sekwencji rzeczy do zrobienia, „kompiluj” ją.
- To przeprojektowanie zostało wykonane, zmniejszając kod źródłowy czterokrotnie, a czas został skrócony do 10 sekund.
Teraz, ponieważ robi się tak szybko, trudno jest próbkować, więc daję mu 10 razy więcej do zrobienia, ale poniższe czasy są oparte na oryginalnym obciążeniu.
Więcej diagnozy ujawnia, że spędza czas na zarządzaniu kolejkami. Podszewka skraca czas do 7 sekund.
Teraz dużym poświęceniem czasu jest druk diagnostyczny, który robiłem. Spłucz to - 4 sekundy.
Teraz największymi odbiorcami czasu są połączenia z malloc i bezpłatne . Przetwarzaj obiekty - 2,6 sekundy.
Kontynuując próbkowanie, wciąż znajduję operacje, które nie są absolutnie konieczne - 1,1 sekundy.
Współczynnik całkowitego przyspieszenia: 43,6
Teraz nie ma dwóch podobnych programów, ale w oprogramowaniu innym niż zabawkowe zawsze widziałem taki postęp. Najpierw dostajesz rzeczy łatwe, a potem trudniejsze, aż dojdziesz do punktu malejących zysków. Wtedy zdobyta wiedza może doprowadzić do przeprojektowania, rozpoczynając nową rundę przyspieszeń, aż znów osiągniesz malejące zyski. Teraz jest to punkt, w którym może ona sensu zastanawiać się, czy ++i
czy i++
czy for(;;)
czy while(1)
są szybsze: rodzaje pytań widzę tak często na przepełnienie stosu.
PS Można się zastanawiać, dlaczego nie użyłem profilera. Odpowiedź jest taka, że prawie każdy z tych „problemów” był miejscem wywoływania funkcji, które precyzyjnie układa próbki. Profile nawet dzisiaj prawie nie wpadają na pomysł, że instrukcje i instrukcje połączeń są ważniejsze do zlokalizowania i łatwiejsze do naprawy niż całe funkcje.
Właściwie stworzyłem profiler, aby to zrobić, ale dla prawdziwej podejrzanej intymności z tym, co robi kod, nie ma substytutu dla wprawienia w to palców. Nie jest problemem, że liczba próbek jest niewielka, ponieważ żaden ze znalezionych problemów nie jest tak mały, że można je łatwo przeoczyć.
DODANO: jerryjvl poprosił o kilka przykładów. Oto pierwszy problem. Składa się z niewielkiej liczby oddzielnych wierszy kodu, co łącznie zajmuje ponad połowę czasu:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
Używali klastra list ILST (podobnego do klasy listy). Są one wdrażane w zwykły sposób, a „ukrywanie informacji” oznacza, że użytkownicy klasy nie powinni dbać o to, jak zostały zaimplementowane. Kiedy napisano te wiersze (z około 800 wierszy kodu) nie przyszło mi do głowy, że mogą to być „wąskie gardła” (nienawidzę tego słowa). Są po prostu zalecanym sposobem robienia rzeczy. Łatwo powiedzieć z perspektywy czasu, że należy tego unikać, ale z mojego doświadczenia wynika, że wszystkie problemy z wydajnością są takie. Ogólnie rzecz biorąc, dobrze jest unikać tworzenia problemów z wydajnością. Jeszcze lepiej jest znaleźć i naprawić te, które zostały utworzone, nawet jeśli „należało ich unikać” (z perspektywy czasu).
Oto drugi problem w dwóch osobnych wierszach:
/* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)
Są to budowanie list poprzez dołączanie przedmiotów na ich końcach. (Rozwiązaniem było zebranie elementów w tablice i zbudowanie list naraz.) Ciekawe jest to, że te wyciągi kosztują tylko (tj. Były na stosie wywołań) 3/48 pierwotnego czasu, więc nie były w fakt jest dużym problemem na początku . Jednak po usunięciu pierwszego problemu kosztują 3/20 czasu, a więc były teraz „większą rybą”. Ogólnie tak to wygląda.
Mogę dodać, że ten projekt był destylowany z prawdziwego projektu, któremu pomogłem. W tym projekcie problemy z wydajnością były znacznie bardziej dramatyczne (podobnie jak przyspieszenie), takie jak wywołanie procedury dostępu do bazy danych w wewnętrznej pętli, aby sprawdzić, czy zadanie zostało zakończone.
DODANO ODNIESIENIA: Kod źródłowy, zarówno oryginalny, jak i przeprojektowany, można znaleźć na stronie www.ddj.com , dla 1993 r., W pliku 9311.zip, plikach slug.asc i slug.zip.
EDYCJA 2011/11/26: Istnieje teraz projekt SourceForge zawierający kod źródłowy w Visual C ++ i szczegółowy opis jego dostrojenia. Przechodzi tylko przez pierwszą połowę opisanego powyżej scenariusza i nie zachowuje dokładnie tej samej sekwencji, ale wciąż otrzymuje przyspieszenie o 2-3 rzędy wielkości.
Propozycje:
Wady : jeśli kilka wstępnie obliczonych wartości jest rzeczywiście używanych, może to pogorszyć sprawę, również wyszukiwanie może zająć znaczną pamięć.
Wady : pisanie dodatkowego kodu oznacza większą powierzchnię dla błędów.
Wady : Cóż ... odpowiedź nie będzie dokładna.
źródło
Jeśli nie możesz już poprawić wydajności - sprawdź, czy możesz poprawić postrzeganą wydajność.
Możesz nie być w stanie przyspieszyć algorytmu fooCalc, ale często istnieją sposoby, aby Twoja aplikacja wydawała się bardziej responsywna dla użytkownika.
Kilka przykładów:
Nie przyspieszy to działania Twojego programu, ale może sprawić, że użytkownicy będą bardziej zadowoleni z prędkości, którą masz.
źródło
Większość życia spędzam właśnie w tym miejscu. Szerokie pociągnięcia to uruchomienie twojego profilera i zapisanie go:
__restrict
swobodnie, aby obiecać kompilatorowi o aliasingu.I jeszcze jedno, co lubię robić:
źródło
Examples on the PowerPC ...
<- To znaczy niektóre implementacje PowerPC. PowerPC to ISA, a nie CPU.lea
.Dodaj do tego więcej sprzętu!
źródło
Więcej sugestii:
Unikaj I / O : Dowolne I / O (dysk, sieć, porty itp.) Zawsze będzie znacznie wolniejsze niż jakikolwiek kod, który wykonuje obliczenia, więc pozbądź się I / O, których nie potrzebujesz.
Przenieś I / O z góry : Załaduj wszystkie dane, których będziesz potrzebować do obliczeń z góry, abyś nie powtarzał czeków I / O w rdzeniu krytycznego algorytmu (a być może w wyniku tego powtórzy się dysk szuka, podczas ładowania wszystkich danych w jednym trafieniu może uniknąć wyszukiwania).
Opóźnij operacje wejścia / wyjścia : nie zapisuj wyników, dopóki obliczenia się nie zakończą, przechowuj je w strukturze danych, a następnie zrzuć je za jednym zamachem na koniec, gdy ciężka praca zostanie wykonana.
Gwintowane we / wy : dla tych, którzy mają dość odwagi, połącz „we / wy z góry” lub „opóźnij we / wy” z faktycznymi obliczeniami, przenosząc ładowanie do równoległego wątku, aby podczas ładowania większej ilości danych można było pracować na podstawie obliczeń na danych, które już masz, lub podczas obliczania następnej partii danych możesz jednocześnie zapisać wyniki z ostatniej partii.
źródło
mmap()
do wprowadzania danych, wykonuj odpowiedniemadvise()
połączenia i używajaio_write()
do zapisywania dużych porcji danych wyjściowych (= kilka MiB).Ponieważ wiele problemów z wydajnością wiąże się z problemami z bazą danych, dam ci kilka konkretnych rzeczy, na które warto zwrócić uwagę podczas dostrajania zapytań i procedur przechowywanych.
Unikaj kursorów w większości baz danych. Unikaj również zapętlania. Przez większość czasu dostęp do danych powinien być oparty na ustawieniach, a nie rejestrowany przez przetwarzanie rekordów. Obejmuje to nieużywanie procedury przechowywanej z jednym rekordem, gdy chcesz wstawić 1 000 000 rekordów jednocześnie.
Nigdy nie używaj select *, zwracaj tylko te pola, których naprawdę potrzebujesz. Jest to szczególnie prawdziwe, jeśli istnieją sprzężenia, ponieważ pola łączenia będą się powtarzać, co spowoduje niepotrzebne obciążenie zarówno serwera, jak i sieci.
Unikaj używania skorelowanych podkwerend. Użyj sprzężeń (w tym sprzężeń z tabelami pochodnymi, jeśli to możliwe) (wiem, że dotyczy to Microsoft SQL Server, ale przetestuj porady, jeśli używasz innego zaplecza).
Indeks, indeks, indeks. I zaktualizuj te statystyki, jeśli dotyczą twojej bazy danych.
Ustaw zapytanie jako możliwe do wysłania . Oznacza to, że unikaj rzeczy, które uniemożliwiają użycie indeksów, takich jak użycie znaku wieloznacznego w pierwszym znaku klauzuli like lub funkcji w złączeniu lub jako lewej części instrukcji where.
Użyj poprawnych typów danych. Szybsze jest wykonanie obliczeń matematycznych na polu daty niż próba konwersji typu danych ciągu na typ danych daty, a następnie wykonanie obliczeń.
Nigdy nie wkładaj żadnej pętli do wyzwalacza!
Większość baz danych ma sposób na sprawdzenie, jak zostanie wykonane wykonanie zapytania. W Microsoft SQL Server nazywa się to planem wykonania. Sprawdź je najpierw, aby zobaczyć, gdzie leżą obszary problemowe.
Zastanów się, jak często uruchamiane jest zapytanie, a także ile czasu zajmuje jego określenie, co należy zoptymalizować. Czasami możesz uzyskać więcej wyników od drobnych poprawek do zapytania, które jest uruchamiane miliony razy dziennie, niż możesz wyczyścić czas z długiego wybiegania zapytania, które działa tylko raz w miesiącu.
Użyj narzędzia do profilowania, aby dowiedzieć się, co tak naprawdę jest wysyłane do iz bazy danych. Pamiętam, jak kiedyś w przeszłości nie mogliśmy zrozumieć, dlaczego strona tak wolno się ładuje, gdy procedura przechowywana była szybka, i dowiedziałem się przez profilowanie, że strona internetowa wielokrotnie pytała o zapytanie, a nie raz.
Profiler pomoże Ci również ustalić, kto kogo blokuje. Niektóre zapytania, które wykonują się szybko, gdy działają same, mogą stać się bardzo wolne z powodu blokad z innych zapytań.
źródło
Najważniejszym obecnie czynnikiem ograniczającym jest ograniczenie przepustowości pamięci . Multikresy tylko pogarszają sytuację, ponieważ przepustowość jest dzielona między rdzeniami. Również ograniczony obszar chipów przeznaczony na implementację pamięci podręcznej jest również podzielony między rdzenie i wątki, co jeszcze bardziej pogarsza ten problem. Wreszcie, wraz ze wzrostem liczby rdzeni rośnie także sygnalizacja między chipami potrzebna do utrzymania spójności różnych pamięci podręcznych. Dodaje to również karę.
To są efekty, którymi musisz zarządzać. Czasami przez mikro zarządzanie kodem, ale czasem przez staranne rozważenie i refaktoryzację.
Wiele komentarzy już wspomina o kodzie przyjaznym dla pamięci podręcznej. Istnieją co najmniej dwa wyraźne smaki:
Pierwszy problem dotyczy w szczególności regularności wzorców dostępu do danych, umożliwiając wydajne działanie preselektora sprzętowego. Unikaj dynamicznej alokacji pamięci, która rozkłada obiekty danych w pamięci. Używaj kontenerów liniowych zamiast połączonych list, skrótów i drzew.
Drugi problem dotyczy poprawy ponownego wykorzystania danych. Zmień algorytmy, aby działały na podzbiorach danych, które mieszczą się w dostępnej pamięci podręcznej, i ponownie wykorzystaj te dane w jak największym stopniu, dopóki są one w pamięci podręcznej.
Lepsze pakowanie danych i upewnienie się, że wszystkie dane są używane w liniach pamięci podręcznej w gorących pętlach, pomogą uniknąć tych innych efektów i pozwolą dopasować bardziej przydatne dane w pamięci podręcznej.
źródło
źródło
Mimo że podoba mi się odpowiedź Mike'a Dunlavey'a, w rzeczywistości jest to świetna odpowiedź ze wspierającym przykładem, ale myślę, że można ją wyrazić bardzo prosto w ten sposób:
Dowiedz się, co zajmuje najwięcej czasu, i zrozum, dlaczego.
Jest to proces identyfikacji wieprzy czasu, który pomaga zrozumieć, gdzie należy udoskonalić algorytm. To jedyna wszechstronna odpowiedź agnostyczna na język, jaką mogę znaleźć na problem, który powinien być już w pełni zoptymalizowany. Zakładając również, że chcesz być niezależny od architektury w dążeniu do szybkości.
Chociaż algorytm może być zoptymalizowany, jego implementacja może nie być. Identyfikacja pozwala dowiedzieć się, która część jest która: algorytm lub implementacja. Więc który z nich jest najbardziej czas, jest twoim głównym kandydatem do przeglądu. Ale ponieważ mówisz, że chcesz wycisnąć ostatnie kilka%, możesz również zbadać mniejsze części, części, których na początku nie zbadałeś dokładnie.
Na koniec trochę prób i błędów z danymi liczbowymi dotyczącymi wydajności różnych sposobów implementacji tego samego rozwiązania lub potencjalnie różnych algorytmów może dostarczyć informacji, które pomogą zidentyfikować straty czasu i oszczędność czasu.
HPH, asoudmove.
źródło
Prawdopodobnie powinieneś wziąć pod uwagę „perspektywę Google”, tj. Określić, w jaki sposób Twoja aplikacja może zostać w dużej mierze zrównoleglona i współbieżna, co nieuchronnie oznacza również, że w pewnym momencie przyjrzysz się dystrybucji Twojej aplikacji między różnymi maszynami i sieciami, aby idealnie skalować ją prawie liniowo ze sprzętem, który rzucisz na niego.
Z drugiej strony ludzie Google są również znani z rzucania dużej siły roboczej i zasobów przy rozwiązywaniu niektórych problemów w projektach, narzędziach i infrastrukturze, z których korzystają, takich jak na przykład optymalizacja całego programu dla gcc przez oddany zespół inżynierów włamywanie się do wewnętrznych komponentów gcc w celu przygotowania go do typowych dla Google scenariuszy przypadków użycia.
Podobnie profilowanie aplikacji nie oznacza już po prostu profilowania kodu programu, ale także wszystkich otaczających go systemów i infrastruktury (sieci myślowe, przełączniki, serwer, macierze RAID) w celu zidentyfikowania redundancji i potencjału optymalizacji z punktu widzenia systemu.
źródło
źródło
źródło
Dziel i rządź
Jeśli przetwarzany zestaw danych jest zbyt duży, zapętl go. Jeśli poprawnie wykonałeś swój kod, wdrożenie powinno być łatwe. Jeśli masz program monolityczny, teraz wiesz lepiej.
źródło
Przede wszystkim, jak wspomniano w kilku wcześniejszych odpowiedziach, dowiedz się, co gryzie Twoją wydajność - czy to pamięć, procesor, sieć, baza danych czy coś innego. W zależności od tego ...
... jeśli to pamięć - znajdź jedną z książek napisanych dawno temu przez Knutha, jednej z serii „The Art of Computer Programming”. Najprawdopodobniej chodzi o sortowanie i wyszukiwanie - jeśli moja pamięć jest zła, musisz dowiedzieć się, w jaki sposób mówi o tym, jak radzić sobie z wolnym przechowywaniem danych na taśmie. Mentalnie przekształć jego pamięć / taśmę parę w parę pamięci podręcznej / pamięci głównej (lub pary pamięci podręcznej L1 / L2). Przestudiuj wszystkie sztuczki, które opisuje - jeśli nie znajdziesz czegoś, co rozwiąże twój problem, zatrudnij profesjonalnego informatyka, który przeprowadzi profesjonalne badanie. Jeśli problem z pamięcią jest przypadkowo związany z FFT (pamięć podręczna jest pomijana przy indeksach odwróconych bitów podczas wykonywania motyli Radix-2), nie zatrudniaj naukowca - zamiast tego ręcznie zoptymalizuj podania jeden po drugim, aż „ do ostatnich kilku procent, prawda? Jeśli jest ich niewiele , najprawdopodobniej wygrasz.
... jeśli to procesor - przełącz się na język asemblera. Badanie specyfikacji procesora - co wymaga tików , VLIW, SIMD. Wywołania funkcji to najprawdopodobniej wymienialne zjadacze kleszczy. Naucz się transformacji pętli - potok, rozwijaj. Mnożenia i podziały mogą być wymienne / interpolowane z przesunięciami bitów (mnożenia przez małe liczby całkowite mogą być zastępowane dodatkami). Wypróbuj sztuczki z krótszymi danymi - jeśli masz szczęście, jedna instrukcja z 64 bitami może okazać się wymienna na dwie na 32 lub nawet 4 na 16 lub 8 na 8 bitów. Spróbuj także dłużejdane - np. obliczenia zmiennoprzecinkowe mogą okazać się wolniejsze niż podwójne w przypadku konkretnego procesora. Jeśli masz elementy trygonometryczne, walcz z nimi przy użyciu wstępnie obliczonych tabel; należy również pamiętać, że sinus o małej wartości można zastąpić tą wartością, jeśli utrata precyzji mieści się w dozwolonych granicach.
... jeśli jest to sieć - pomyśl o kompresji przekazywanych danych. Zamień transfer XML na binarny. Opracuj protokoły. Wypróbuj UDP zamiast TCP, jeśli możesz w jakiś sposób poradzić sobie z utratą danych.
... jeśli to baza danych, to przejdź do dowolnego forum bazy danych i poproś o radę. Siatka danych w pamięci, optymalizacja planu zapytań itp. Itd.
HTH :)
źródło
Buforowanie! Tani sposób (w wysiłku programisty) na przyspieszenie prawie wszystkiego to dodanie warstwy abstrakcji pamięci podręcznej do dowolnego obszaru przenoszenia danych w twoim programie. Czy to we / wy, czy po prostu przekazywanie / tworzenie obiektów lub struktur. Często łatwo jest dodawać bufory do klas fabrycznych i czytników / pisarzy.
Czasami pamięć podręczna nie przyniesie wiele korzyści, ale łatwo jest po prostu dodać buforowanie w całości, a następnie wyłączyć go tam, gdzie to nie pomaga. Często stwierdziłem, że osiąga to ogromną wydajność bez konieczności mikroanalizy kodu.
źródło
Myślę, że zostało to już powiedziane w inny sposób. Ale gdy masz do czynienia z algorytmem intensywnie wykorzystującym procesor, powinieneś uprościć wszystko w najbardziej wewnętrznej pętli kosztem wszystkiego innego.
Dla niektórych może się to wydawać oczywiste, ale staram się skupić na tym, niezależnie od języka, z którym pracuję. Jeśli na przykład masz do czynienia z zagnieżdżonymi pętlami i znajdujesz możliwość obniżenia poziomu kodu, w niektórych przypadkach możesz znacznie go przyspieszyć. Innym przykładem są małe rzeczy, o których warto pomyśleć, np. Praca z liczbami całkowitymi zamiast zmiennych zmiennoprzecinkowych, gdy tylko jest to możliwe, i używanie mnożenia zamiast dzielenia, gdy tylko jest to możliwe. Ponownie, są to rzeczy, które należy wziąć pod uwagę w swojej najbardziej wewnętrznej pętli.
Czasami może się przydać wykonywanie operacji matematycznych na liczbie całkowitej w wewnętrznej pętli, a następnie skalowanie jej do zmiennej zmiennoprzecinkowej, z którą można później pracować. To przykład poświęcenia prędkości w jednej sekcji, aby poprawić prędkość w innej, ale w niektórych przypadkach opłacenie może być tego warte.
źródło
Spędziłem trochę czasu pracując nad optymalizacją systemów biznesowych klient / serwer działających w sieciach o niskiej przepustowości i długich opóźnieniach (np. Satelitarnych, zdalnych, na morzu) i byłem w stanie osiągnąć znaczną poprawę wydajności przy dość powtarzalnym procesie.
Zmierz : zacznij od zrozumienia podstawowej pojemności i topologii sieci. Rozmawiając z odpowiednimi pracownikami sieci w branży i korzystaj z podstawowych narzędzi, takich jak ping i traceroute, aby ustalić (co najmniej) opóźnienie sieci z każdej lokalizacji klienta, podczas typowych okresów operacyjnych. Następnie dokonaj dokładnych pomiarów czasu określonych funkcji użytkownika końcowego, które wyświetlają problematyczne objawy. Zapisz wszystkie te pomiary wraz z ich lokalizacjami, datami i godzinami. Zastanów się nad wbudowaniem w aplikację kliencką funkcji „testowania wydajności sieci” przez użytkownika końcowego, umożliwiając zaawansowanym użytkownikom uczestnictwo w procesie doskonalenia; wzmocnienie ich w ten sposób może mieć ogromny wpływ psychologiczny, gdy masz do czynienia z użytkownikami sfrustrowanymi przez słabo działający system.
Analizować : użycie dowolnej dostępnej metody rejestrowania w celu ustalenia, jakie dane są przesyłane i odbierane podczas wykonywania operacji, których dotyczy problem. W idealnym przypadku aplikacja może przechwytywać dane przesyłane i odbierane zarówno przez klienta, jak i serwer. Jeśli obejmują one także znaczniki czasu, nawet lepiej. Jeśli wystarczające rejestrowanie nie jest dostępne (np. System zamknięty lub niemożność wdrożenia modyfikacji w środowisku produkcyjnym), użyj sniffera sieciowego i upewnij się, że naprawdę rozumiesz, co się dzieje na poziomie sieci.
Pamięć podręczna : poszukaj przypadków, w których dane statyczne lub rzadko zmieniane są przesyłane powtarzalnie, i rozważ odpowiednią strategię buforowania. Typowe przykłady obejmują wartości „listy wyboru” lub inne „jednostki referencyjne”, które mogą być zaskakująco duże w niektórych aplikacjach biznesowych. W wielu przypadkach użytkownicy mogą zaakceptować konieczność ponownego uruchomienia lub odświeżenia aplikacji, aby zaktualizować rzadko aktualizowane dane, szczególnie jeśli może to znacznie ograniczyć czas wyświetlania często używanych elementów interfejsu użytkownika. Upewnij się, że rozumiesz prawdziwe zachowanie już wdrożonych elementów pamięci podręcznej - wiele powszechnych metod buforowania (np. HTTP ETag) wciąż wymaga obrócenia sieci w celu zapewnienia spójności, a tam, gdzie opóźnienie sieci jest kosztowne, możesz być w stanie tego uniknąć dzięki inne podejście do buforowania.
Równoległość : poszukaj transakcji sekwencyjnych, które nie muszą być logicznie wydawane ściśle sekwencyjnie, i przerób system, aby wydawać je równolegle. Miałem do czynienia z jednym przypadkiem, w którym żądanie typu end-to-end miało nieodłączne opóźnienie sieciowe wynoszące ~ 2s, co nie stanowiło problemu dla pojedynczej transakcji, ale gdy wymagane było 6 sekwencyjnych 2-sekundowych podróży w obie strony, zanim użytkownik odzyskał kontrolę nad aplikacją kliencką stało się ogromnym źródłem frustracji. Odkrycie, że transakcje te były w rzeczywistości niezależne, pozwoliło na ich równoległe wykonywanie, zmniejszając opóźnienie użytkownika końcowego do poziomu bardzo zbliżonego do kosztu pojedynczej podróży w obie strony.
Łącz : tam, gdzie sekwencyjne żądania muszą być wykonywane sekwencyjnie, poszukaj okazji, aby połączyć je w jedno bardziej kompleksowe żądanie. Typowe przykłady obejmują tworzenie nowych jednostek, a następnie żądania powiązania tych jednostek z innymi istniejącymi jednostkami.
Kompresuj : poszukaj możliwości wykorzystania kompresji ładunku, zastępując formularz tekstowy binarnym lub używając faktycznej technologii kompresji. Wiele nowoczesnych (tj. W ciągu dekady) stosów technologii obsługuje to prawie przezroczysto, więc upewnij się, że jest skonfigurowane. Często byłem zaskoczony znaczącym wpływem kompresji, gdy wydawało się jasne, że problemem były zasadniczo opóźnienia, a nie przepustowość, odkrywając po fakcie, że pozwoliło to na dopasowanie transakcji do jednego pakietu lub w inny sposób uniknęło utraty pakietu, a zatem ma duży rozmiar wpływ na wydajność.
Powtórz : wróć na początek i ponownie zmierz swoje operacje (w tych samych lokalizacjach i czasach) dzięki wprowadzonym ulepszeniom, rejestruj i raportuj swoje wyniki. Jak w przypadku każdej optymalizacji, niektóre problemy mogły zostać rozwiązane, odsłaniając inne, które teraz dominują.
W powyższych krokach skupiam się na procesie optymalizacji związanej z aplikacją, ale oczywiście musisz upewnić się, że sama sieć bazowa jest skonfigurowana w najbardziej efektywny sposób, aby obsługiwać również twoją aplikację. Zaangażuj specjalistów sieciowych w branży i ustal, czy są w stanie zastosować poprawę wydajności, QoS, kompresję sieci lub inne techniki rozwiązania tego problemu. Zwykle nie rozumieją potrzeb aplikacji, dlatego ważne jest, abyś był przygotowany (po etapie analizy) do omówienia go z nimi, a także do uzasadnienia wszelkich kosztów, które będą musieli ponieść. . Napotkałem przypadki, w których błędna konfiguracja sieci spowodowała, że dane aplikacji były przesyłane powolnym łączem satelitarnym, a nie łączem lądowym, po prostu dlatego, że korzystał z portu TCP, który nie był „dobrze znany” przez specjalistów od sieci; oczywiście usunięcie takiego problemu może mieć dramatyczny wpływ na wydajność, bez konieczności wprowadzania kodu oprogramowania lub zmian w konfiguracji.
źródło
Bardzo trudno jest udzielić ogólnej odpowiedzi na to pytanie. To zależy od domeny problemu i implementacji technicznej. Ogólna technika, która jest dość neutralna dla języka: Identyfikuj punkty aktywne kodu, których nie można wyeliminować, i optymalizuj ręcznie kod asemblera.
źródło
Ostatnie kilka% jest bardzo zależne od procesora i aplikacji ....
Lista jest długa ... Ale takie rzeczy naprawdę są ostatecznością ...
Kompiluj dla x86 i uruchom Valgrind / Cachegrind z kodem, aby poprawnie profilować wydajność. Lub CCStudio z Texas Instruments ma słodki profiler. Wtedy naprawdę będziesz wiedział, gdzie się skupić ...
źródło
Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?
W przypadku projektów nie offline, mając najlepsze oprogramowanie i najlepszy sprzęt, jeśli twoja przepustowość jest słaba, to ta cienka linia będzie ściskać dane i zapewniać opóźnienia, choć w milisekundach ... ale jeśli mówisz o ostatnich kroplach , to pewne zyski, 24/7 dla każdej wysłanej lub otrzymanej paczki.
źródło
Nie tak dogłębnie lub skomplikowane jak poprzednie odpowiedzi, ale oto: (są to bardziej poziomy dla początkujących / średnio zaawansowanych)
źródło
Nie można powiedzieć. To zależy od tego, jak wygląda kod. Jeśli możemy założyć, że kod już istnieje, możemy po prostu na niego spojrzeć i dowiedzieć się, jak go zoptymalizować.
Lepsza lokalizacja pamięci podręcznej, rozwijanie pętli, Spróbuj wyeliminować długie łańcuchy zależności, aby uzyskać lepszą równoległość na poziomie instrukcji. Preferuj ruchy warunkowe nad gałęziami, jeśli to możliwe. W miarę możliwości korzystaj z instrukcji SIMD.
Zrozum, co robi Twój kod i zrozum, na jakim sprzęcie działa. Następnie ustalenie, co należy zrobić, aby poprawić wydajność kodu, staje się dość proste. To naprawdę jedyna naprawdę ogólna rada, jaką mogę wymyślić.
Cóż, to i „Pokaż kod na SO i poproś o porady dotyczące optymalizacji dla tego konkretnego fragmentu kodu”.
źródło
Jeśli lepszym sprzętem jest opcja, zdecydowanie skorzystaj z niej. Inaczej
źródło
Sposób Google jest jedną z opcji „Buforuj go .. Jeśli to możliwe, nie dotykaj dysku”
źródło
Oto kilka szybkich i brudnych technik optymalizacji, których używam. Uważam to za optymalizację „pierwszego przejścia”.
Dowiedz się, gdzie spędzany jest czas Dowiedz się dokładnie, co zajmuje czas. Czy to plik IO? Czy to czas procesora? Czy to jest sieć? Czy to baza danych? Optymalizacja pod kątem IO jest bezużyteczna, jeśli nie jest to wąskim gardłem.
Poznaj swoje środowisko Wiedza na temat optymalizacji zazwyczaj zależy od środowiska programistycznego. Na przykład w VB6 przekazywanie przez referencję jest wolniejsze niż przekazywanie przez wartość, ale w C i C ++ przez referencję jest znacznie szybsze. W C rozsądne jest wypróbowanie czegoś i zrobienie czegoś innego, jeśli kod powrotu wskazuje awarię, podczas gdy w Dot Net wychwytywanie wyjątków jest znacznie wolniejsze niż sprawdzanie poprawności warunku przed próbą.
Indeksy Twórz indeksy na często wyszukiwanych polach bazy danych. Prawie zawsze możesz wymienić przestrzeń na szybkość.
Unikaj wyszukiwania Wewnątrz pętli, aby zoptymalizować, unikam konieczności wyszukiwania. Znajdź przesunięcie i / lub indeks poza pętlą i ponownie wykorzystaj dane w środku.
Minimalizuj IO, staraj się projektować w sposób, który zmniejsza liczbę operacji odczytu lub zapisu, szczególnie przez połączenie sieciowe
Zmniejsz liczbę abstrakcji Im więcej warstw abstrakcji musi przejść kod, tym jest on wolniejszy. Wewnątrz pętli krytycznej zmniejsz abstrakcje (np. Ujawnij metody niższego poziomu, które unikają dodatkowego kodu)
Odradzaj wątki w projektach z interfejsem użytkownika, tworzenie nowego wątku w celu wykonywania wolniejszych zadań powoduje, że aplikacja wydaje się bardziej responsywna, chociaż nie jest.
Proces wstępny Zasadniczo możesz wymienić przestrzeń na szybkość. Jeśli istnieją obliczenia lub inne intensywne operacje, sprawdź, czy możesz wstępnie obliczyć niektóre informacje, zanim znajdziesz się w krytycznej pętli.
źródło
Jeśli masz dużo równoległych matematyki zmiennoprzecinkowej - szczególnie pojedynczej precyzji - spróbuj oddzielić ją do procesora graficznego (jeśli jest obecny) za pomocą OpenCL lub (dla układów NVidia) CUDA. Procesory graficzne mają w swoich modułach cieniujących ogromną moc obliczeń zmiennoprzecinkowych, która jest znacznie większa niż w przypadku procesora.
źródło
Dodanie tej odpowiedzi, ponieważ nie widziałem, aby było zawarte we wszystkich pozostałych.
Minimalizuj niejawną konwersję między typami i znakiem:
Dotyczy to przynajmniej C / C ++, nawet jeśli już myślisz że jesteś wolny od konwersji - czasem dobrze jest przetestować dodawanie ostrzeżeń kompilatora wokół funkcji wymagających wydajności, szczególnie uważaj na konwersje w pętlach.
Specyficzny dla GCC: Możesz to przetestować, dodając do kodu kilka pełnych pragnień,
Widziałem przypadki, w których można uzyskać kilka procent przyspieszenia, zmniejszając liczbę konwersji wywołanych przez takie ostrzeżenia.
W niektórych przypadkach mam nagłówek ze ścisłymi ostrzeżeniami, które stale dołączam, aby zapobiec przypadkowym konwersjom, ale jest to kompromis, ponieważ możesz w końcu dodać wiele rzutów do cichych celowych konwersji, co może po prostu sprawić, że kod będzie bardziej zaśmiecony zyski.
źródło
Czasami może pomóc zmiana układu danych. W C możesz przełączyć się z tablicy lub struktur na strukturę tablic lub odwrotnie.
źródło
Popraw system operacyjny i strukturę.
Może to zabrzmieć przesadnie, ale pomyśl o tym w następujący sposób: systemy operacyjne i frameworki są zaprojektowane do robienia wielu rzeczy. Twoja aplikacja robi tylko bardzo konkretne rzeczy. Jeśli możesz sprawić, aby system operacyjny zrobił dokładnie to, czego potrzebuje twoja aplikacja i aby Twoja aplikacja zrozumiała, jak działa framework (php, .net, java), możesz znacznie lepiej wykorzystać swój sprzęt.
Na przykład Facebook zmienił niektóre rzeczy na poziomie jądra w Linuksie, zmienił sposób działania memcached (na przykład napisał proxy memcached i użył udp zamiast tcp ).
Innym przykładem jest Window2008. Win2K8 ma wersję, w której można zainstalować tylko podstawowy system operacyjny wymagany do uruchamiania aplikacji X (np. Aplikacje sieciowe, aplikacje serwerowe). Zmniejsza to znaczną część narzutu, jaki system operacyjny ma na uruchamianie procesów i zapewnia lepszą wydajność.
Oczywiście zawsze powinieneś wrzucić więcej sprzętu jako pierwszy krok ...
źródło