Ostateczne strategie optymalizacji wydajności [zamknięte]


609

Na tej stronie jest już wiele pytań dotyczących wydajności, ale przychodzi mi do głowy, że prawie wszystkie są bardzo specyficzne dla problemu i dość wąskie. I prawie wszyscy powtarzają porady, aby uniknąć przedwczesnej optymalizacji.

Załóżmy:

  • kod już działa poprawnie
  • wybrane algorytmy są już optymalne do okoliczności problemu
  • kod został zmierzony, a szkodliwe procedury zostały odizolowane
  • wszystkie próby optymalizacji będą również mierzone, aby upewnić się, że nie pogorszą sprawy

To, czego tu szukam, to strategie i sztuczki, które pozwolą wykorzystać ostatnie kilka procent w algorytmie krytycznym, gdy nie pozostaje nic innego, jak tylko to, co trzeba.

Najlepiej byłoby, gdyby odpowiedzi były niezależne od języka i wskazywały wady sugerowanych strategii, tam gdzie ma to zastosowanie.

Dodam odpowiedź z własnymi wstępnymi sugestiami i czekam na wszystko, co może wymyślić społeczność przepełnienia stosu.

Odpowiedzi:


427

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 ++iczy 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.


3
Chciałbym przeczytać niektóre szczegóły powyższych kroków. Czy można uwzględnić niektóre fragmenty optymalizacji smaku? (bez zbyt długiego postu?)
jerryjvl

8
... Napisałem też książkę, która jest już wyczerpana, więc na Amazon będzie śmieszna cena - „Budowanie lepszych aplikacji” ISBN 0442017405. Zasadniczo ten sam materiał znajduje się w pierwszym rozdziale.
Mike Dunlavey

3
@ Mike Dunlavey, sugeruję poinformowanie Google, że masz go już zeskanowane. Prawdopodobnie mają już umowę z kimkolwiek, kto kupił twojego wydawcę.
Thorbjørn Ravn Andersen

19
@ Thorbjørn: Dla przypomnienia, połączyłem się z GoogleBooks, wypełniłem wszystkie formularze i wysłałem im wydruk. Otrzymałem e-mail z pytaniem, czy naprawdę posiadam prawa autorskie. Wydawca Van Nostrand Reinhold, który został kupiony przez International Thompson, który został kupiony przez Reutersa, a kiedy próbuję do nich zadzwonić lub wysłać e-mailem, to jest jak czarna dziura. Więc jest w otchłani - nie miałem jeszcze energii, żeby naprawdę go ścigać.
Mike Dunlavey,


188

Propozycje:

  • Przed obliczeniem zamiast ponownego obliczenia : wszelkie pętle lub powtarzane wywołania, które zawierają obliczenia o względnie ograniczonym zakresie danych wejściowych, rozważ wykonanie wyszukiwania (tablica lub słownik), które zawiera wynik tego obliczenia dla wszystkich wartości z prawidłowego zakresu wejścia. Następnie zastosuj proste wyszukiwanie w algorytmie.
    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ęć.
  • Nie używaj metod bibliotecznych : większość bibliotek musi być napisana, aby działać poprawnie w szerokim zakresie scenariuszy i przeprowadzać zerowe sprawdzanie parametrów itp. Ponowne wdrożenie metody może być w stanie wyeliminować wiele logiki, która nie ma zastosowania w dokładnie takich okolicznościach, w jakich go używasz.
    Wady : pisanie dodatkowego kodu oznacza większą powierzchnię dla błędów.
  • Stosuj metody biblioteczne : aby się zaprzeczyć, biblioteki językowe są pisane przez ludzi, którzy są o wiele mądrzejsi niż ty lub ja; są szanse, że zrobili to lepiej i szybciej. Nie wdrażaj go samodzielnie, chyba że rzeczywiście możesz go przyspieszyć (np. Zawsze mierz!)
  • Oszustwo : w niektórych przypadkach może istnieć dokładna kalkulacja twojego problemu, możesz nie potrzebować „dokładnej”, czasem przybliżenie może być „wystarczająco dobre” i znacznie szybsze w umowie. Zadaj sobie pytanie, czy to naprawdę ma znaczenie, jeśli odpowiedź zostanie udzielona o 1%? 5% nawet 10%?
    Wady : Cóż ... odpowiedź nie będzie dokładna.

32
Wstępne obliczenia nie zawsze pomagają, a czasem mogą nawet zaszkodzić - jeśli twoja tabela odnośników jest zbyt duża, może zabić wydajność pamięci podręcznej.
Adam Rosenfield

37
Oszukiwanie może często być wygraną. Miałem proces korekcji koloru, który w rdzeniu był 3-wektorowy z matrycą 3x3. Procesor miał mnożoną matrycę sprzętowo, która pominęła niektóre z krzyżówek i poszła naprawdę szybko w porównaniu do wszystkich innych sposobów, ale obsługiwał tylko matryce 4x4 i 4 wektory zmiennoprzecinkowe. Zmiana kodu w celu przeniesienia dodatkowego pustego miejsca i przekształcenie obliczeń na zmiennoprzecinkowe ze stałego punktu pozwoliło na nieco mniej dokładny, ale znacznie szybszy wynik.
RBerteig

6
Oszukiwanie polegało na zastosowaniu mnożenia macierzy, które pominęło niektóre z wewnętrznych produktów, dzięki czemu możliwe było zaimplementowanie w mikrokodzie pojedynczej instrukcji procesora, która zakończyła się szybciej, niż nawet równoważna sekwencja pojedynczych instrukcji. Jest to oszustwo, ponieważ nie ma „poprawnej” odpowiedzi, tylko odpowiedź, która jest „wystarczająco poprawna”.
RBerteig

6
@RBerteig: po prostu „wystarczająco poprawne” to szansa na optymalizację, której większość ludzi tęskni za moim doświadczeniem.
Martin Thompson

5
Nie zawsze możesz założyć, że wszyscy są bardziej inteligentni od ciebie. Na koniec wszyscy jesteśmy profesjonalistami. Możesz jednak założyć, że konkretna biblioteka, której używasz, istnieje i dotarła do twojego środowiska ze względu na jej jakość, dlatego pisanie tej biblioteki musi być bardzo dokładne, nie możesz tego zrobić również dlatego, że nie jesteś w tym wyspecjalizowany pole, a ty nie inwestujesz w ten sam czas. Nie dlatego, że jesteś mniej inteligentny. daj spokój.
v.oddou

164

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:

  • przewidując, czego zażąda użytkownik, i zacznij nad tym pracować
  • wyświetlanie wyników w miarę ich wprowadzania zamiast wszystkich naraz na końcu
  • Dokładny miernik postępu

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.


27
Przyspieszanie paska postępu na końcu może być postrzegane jako szybsze niż absolutnie dokładne. W „Rethinking the Progress Bar” (2007) Harrison, Amento, Kuznetsov i Bell testują wiele rodzajów pasków na grupie użytkowników, a także omawiają niektóre sposoby zmiany kolejności operacji, aby postęp mógł być postrzegany jako szybszy.
Emil Vikström

9
naxa, większość pasków postępu jest fałszywa, ponieważ przewidywanie wielu bardzo różnych kroków przepływu w jednym procencie jest trudne, a czasem niemożliwe. Spójrz tylko na te wszystkie słupki, które utknęły przy 99% :-(
Emil Vikström

138

Większość życia spędzam właśnie w tym miejscu. Szerokie pociągnięcia to uruchomienie twojego profilera i zapisanie go:

  • Pamięć podręczna nie trafia . Pamięć podręczna danych jest źródłem nr 1 w większości programów. Popraw wskaźnik trafień w pamięci podręcznej poprzez reorganizację szkodliwych struktur danych w celu uzyskania lepszej lokalizacji; spakuj struktury i typy numeryczne, aby wyeliminować zmarnowane bajty (a tym samym zmarnowane pobrania pamięci podręcznej); w miarę możliwości pobieraj wstępnie dane, aby zmniejszyć liczbę przeciągnięć.
  • Sklepy z hitami . Założenia kompilatora dotyczące aliasingu wskaźnika i przypadki przenoszenia danych między odłączonymi zestawami rejestrów za pośrednictwem pamięci mogą powodować pewne patologiczne zachowanie, które powoduje, że cały potok procesora jest czyszczony przy obciążeniu op. Znajdź miejsca, w których spławiki, wektory i wartości wewnętrzne są rzutowane na siebie i wyeliminuj je. Użyj __restrictswobodnie, aby obiecać kompilatorowi o aliasingu.
  • Operacje mikrokodowane . Większość procesorów ma pewne operacje, których nie można potokować, ale zamiast tego uruchamiają mały podprogram przechowywany w pamięci ROM. Przykładami PowerPC są: mnożenie liczb całkowitych, dzielenie i zmiana według ilości zmiennej. Problem polega na tym, że cały potok przestaje działać podczas wykonywania tej operacji. Spróbuj wyeliminować użycie tych operacji lub przynajmniej rozbić je na składowe potokowe operacje, aby uzyskać korzyść z superskalarnej wysyłki niezależnie od tego, co robi reszta twojego programu.
  • Oddział nieprzewidziany . Te też opróżniają rurociąg. Znajdź przypadki, w których procesor spędza dużo czasu na uzupełnianiu potoku po gałęzi, i skorzystaj z podpowiedzi gałęzi, jeśli jest dostępna, aby częściej przewidywać poprawnie. Lub jeszcze lepiej, w miarę możliwości zastępuj gałęzie ruchami warunkowymi, szczególnie po operacjach zmiennoprzecinkowych, ponieważ ich rura jest zwykle głębsza, a odczytywanie flag stanu po fcmp może spowodować przeciągnięcie.
  • Sekwencyjne operacje zmiennoprzecinkowe . Zrób te SIMD.

I jeszcze jedno, co lubię robić:

  • Ustaw kompilator tak, aby wyświetlał listy zestawów i spójrz na to, co emituje dla funkcji punktu dostępowego w kodzie. Wszystkie te sprytne optymalizacje, które „dobry kompilator powinien zrobić automatycznie”? Możliwe, że twój kompilator ich nie robi. Widziałem, jak GCC emituje naprawdę kod WTF.

8
Najczęściej używam Intel VTune i PIX. Nie mam pojęcia, czy mogą dostosować się do C #, ale tak naprawdę po uzyskaniu warstwy abstrakcji JIT większość z tych optymalizacji jest poza twoim zasięgiem, z wyjątkiem poprawy lokalizacji pamięci podręcznej i uniknięcia niektórych gałęzi.
Crashworks

6
Mimo to sprawdzenie danych wyjściowych po JIT może pomóc ustalić, czy istnieją konstrukty, które po prostu nie optymalizują się dobrze na etapie JIT ... dochodzenie nigdy nie zaszkodzi, nawet jeśli okaże się ślepy zaułek.
jerryjvl

5
Myślę, że wiele osób, w tym ja, byłoby zainteresowanych tym „zestawem wtf” wyprodukowanym przez gcc. Twoja brzmi jak bardzo interesująca praca :)
BlueRaja - Danny Pflughoeft

1
Examples on the PowerPC ...<- To znaczy niektóre implementacje PowerPC. PowerPC to ISA, a nie CPU.
Billy ONeal,

1
@BillyONeal Nawet na nowoczesnym sprzęcie x86, imul może zatrzymać proces; patrz „Instrukcja obsługi optymalizacji architektury Intel® 64 i IA-32” §13.3.2.3: „Instrukcja mnożenia liczby całkowitej trwa kilka cykli. Są one przetwarzane potokowo w taki sposób, że instrukcja mnożenia liczby całkowitej i inna instrukcja długiego opóźnienia mogą robić postępy w faza wykonania. Jednak instrukcje mnożenia liczb całkowitych będą blokować wydawanie innych instrukcji liczb całkowitych pojedynczego cyklu z powodu wymogu zamówienia programu. ” Dlatego zwykle lepiej jest używać tablic o rozmiarach i lea.
Crashworks

78

Dodaj do tego więcej sprzętu!


30
więcej sprzętu nie zawsze jest opcją, jeśli masz oprogramowanie, które ma działać na sprzęcie już w terenie.
Doug T.

76
Niezbyt pomocna odpowiedź dla kogoś, kto tworzy oprogramowanie konsumenckie: klient nie będzie chciał usłyszeć, jak mówisz „kup szybszy komputer”. Zwłaszcza jeśli piszesz oprogramowanie ukierunkowane na konsolę do gier wideo.
Crashworks

19
@Crashworks, a właściwie system osadzony. Kiedy ostatnia funkcja jest już dostępna i pierwsza partia płyt jest już odwrócona, nie jest moment, aby odkryć, że powinieneś był użyć szybszego procesora w pierwszej kolejności ...
RBerteig

71
Kiedyś musiałem debugować program, który miał ogromny wyciek pamięci - jego wielkość maszyny wirtualnej rosła o około 1 Mb na godzinę. Kolega żartował, że wszystko, co musiałem zrobić, to dodawać pamięć w stałym tempie . :)
j_random_hacker

9
Więcej sprzętu: ah tak, mierna linia życia programisty. Nie wiem, ile razy słyszałem „dodaj kolejną maszynę i podwoj pojemność!”
Olof Forshell

58

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.


3
Zauważ, że „przeniesienie IO do równoległego wątku” powinno być wykonane jako asynchroniczne IO na wielu platformach (np. Windows NT).
Billy ONeal

2
We / wy jest rzeczywiście punktem krytycznym, ponieważ jest powolny i ma duże opóźnienia, a dzięki tej radie można przyspieszyć, ale nadal jest on zasadniczo wadliwy: punkty to opóźnienie (które musi być ukryte) i obciążenie systemowe ( co należy zmniejszyć, zmniejszając liczbę połączeń we / wy). Najlepsza rada: używaj mmap()do wprowadzania danych, wykonuj odpowiednie madvise()połączenia i używaj aio_write()do zapisywania dużych porcji danych wyjściowych (= kilka MiB).
cmaster

1
Szczególnie ta ostatnia opcja jest dość łatwa do wdrożenia w Javie. To dało OGROMNY wzrost wydajności aplikacji, które napisałem. Kolejną ważną kwestią (więcej niż przesunięcie we / wy z góry) jest uczynienie go sekwencjalnym i dużych bloków we / wy. Wiele małych odczytów jest znacznie droższych niż 1 duży, ze względu na czas poszukiwania dysku.
BobMcGee

W pewnym momencie oszukałem unikając operacji wejścia / wyjścia, po prostu tymczasowo przenosząc wszystkie pliki na dysk RAM przed obliczeniem i przenosząc je z powrotem. Jest to brudne, ale może być przydatne w sytuacji, gdy nie kontrolujesz logiki, która wykonuje połączenia We / Wy.
MD

48

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ń.


29

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:

  • Unikaj opóźnień pobierania pamięci.
  • Niższe ciśnienie magistrali pamięci (przepustowość).

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.


25
  • Na jakim sprzęcie pracujesz? Czy możesz korzystać z optymalizacji specyficznych dla platformy (takich jak wektoryzacja)?
  • Czy możesz uzyskać lepszy kompilator? Np. Przejść z GCC na Intel?
  • Czy potrafisz uruchomić algorytm równolegle?
  • Czy można zmniejszyć liczbę braków w pamięci podręcznej poprzez reorganizację danych?
  • Czy możesz wyłączyć twierdzenia?
  • Mikrooptymalizacja dla twojego kompilatora i platformy. W stylu „w if / else umieść najczęściej używane stwierdzenie na początku”

4
Powinno być „przejście z GCC na LLVM” :)
Zifre

4
Czy potrafisz uruchomić algorytm równolegle? - obowiązuje również odwrotność
justin

4
To prawda, że ​​zmniejszenie liczby wątków może być równie dobrą optymalizacją
Johan Kotlinski

re: mikrooptymalizacja: jeśli sprawdzisz dane wyjściowe asm kompilatora, często możesz dostosować źródło, aby utrzymać go w lepszym asm. Zobacz Dlaczego ten kod C ++ jest szybszy niż mój odręczny zestaw do testowania przypuszczeń Collatz? aby uzyskać więcej informacji na temat pomocy lub pokonania kompilatora na współczesnym x86.
Peter Cordes,

17

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.


16

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.


15
  • Procedury wbudowane (eliminują wywołanie / powrót i przesuwanie parametrów)
  • Spróbuj wyeliminować testy / przełączniki z wyszukiwaniem tabel (jeśli są one szybsze)
  • Rozwiń pętle (urządzenie Duffa) do punktu, w którym po prostu mieszczą się w pamięci podręcznej procesora
  • Lokalizuj dostęp do pamięci, aby nie zniszczyć pamięci podręcznej
  • Zlokalizuj powiązane obliczenia, jeśli optymalizator tego jeszcze nie robi
  • Wyeliminuj niezmienniki pętli, jeśli optymalizator tego jeszcze nie robi

2
Urządzenie IIRC Duff jest bardzo rzadko szybsze. Tylko wtedy, gdy operacja jest bardzo krótka (jak jedno małe wyrażenie matematyczne)
BCS,

12
  • Kiedy dochodzisz do punktu, że używasz wydajnych algorytmów, pojawia się pytanie o to, czego potrzebujesz więcej prędkości lub pamięci . Użyj pamięci podręcznej, aby „zapłacić” w pamięci, aby zwiększyć prędkość lub użyj obliczeń, aby zmniejszyć ilość pamięci.
  • Jeśli to możliwe (i bardziej opłacalne) rzuć sprzęt na problem - szybszy procesor, więcej pamięci lub HD może rozwiązać problem szybciej niż próba jego kodowania.
  • Jeśli to możliwe, użyj równoległości - uruchom część kodu na wielu wątkach.
  • Użyj odpowiedniego narzędzia do pracy . niektóre języki programowania tworzą bardziej wydajny kod, używając kodu zarządzanego (np. Java / .NET), aby przyspieszyć programowanie, ale natywne języki programowania tworzą szybsze działanie kodu.
  • Mikrooptymalizacja . Miały zastosowanie tylko, można użyć zoptymalizowanego zestawu do przyspieszenia małych fragmentów kodu, użycie optymalizacji SSE / wektora w odpowiednich miejscach może znacznie zwiększyć wydajność.

12

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.


9
+1 za dźwięk „uderzenia” w muchę wodną, ​​który usłyszałem podczas czytania ostatniego zdania.
Bryan Boettcher

11

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 :)


9

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.


8

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.


8

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.


7

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.


7

Ostatnie kilka% jest bardzo zależne od procesora i aplikacji ....

  • architektury pamięci podręcznej różnią się, niektóre układy mają wbudowaną pamięć RAM, którą można bezpośrednio odwzorować, ARM (czasami) mają jednostkę wektorową, SH4 to przydatny kod operacji macierzy. Czy jest GPU - może shader jest dobrym rozwiązaniem. TMS320 są bardzo wrażliwe na gałęzie w pętlach (więc oddziel pętle i jeśli to możliwe, przenieś warunki na zewnątrz).

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ć ...


7

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.


7

Nie tak dogłębnie lub skomplikowane jak poprzednie odpowiedzi, ale oto: (są to bardziej poziomy dla początkujących / średnio zaawansowanych)

  • oczywiste: suche
  • uruchamiaj pętle do tyłu, aby zawsze porównywać do 0, a nie do zmiennej
  • używaj operatorów bitowych, kiedy tylko możesz
  • rozbić powtarzalny kod na moduły / funkcje
  • obiekty pamięci podręcznej
  • zmienne lokalne mają niewielką przewagę wydajności
  • ogranicz manipulację ciągiem tak bardzo, jak to możliwe

4
O zapętlaniu wstecz: tak, porównanie końca pętli będzie szybsze. Zwykle używasz tej zmiennej do indeksowania do pamięci, a dostęp do niej odwrócony może być nieproduktywny z powodu częstych braków pamięci podręcznej (bez pobierania wstępnego).
Andreas Reiff,

1
AFAIK, w większości przypadków, każdy rozsądny optymalizator poradzi sobie dobrze z pętlami, bez konieczności wyraźnego działania programisty w odwrotnej kolejności. Albo optymalizator odwróci samą pętlę, albo ma inny równie dobry sposób. Zauważyłem identyczną wyjście ASM dla (co prawda stosunkowo proste) pętli napisany zarówno rosnąco vs max i zstępujących vs 0. Oczywiście, moi Z80 dni mnie mieć w zwyczaju odruchowo piszących do tyłu pętli, ale podejrzewam, podając go do początkujących jest zwykle śledź czerwony / przedwczesna optymalizacja, gdy czytelny kod i nauka ważniejszych praktyk powinny być priorytetami.
underscore_d

Przeciwnie, uruchamianie pętli wstecz będzie wolniejsze w językach niższego poziomu, ponieważ w wojnie między porównaniem do zera plus dodatkowym odejmowaniem w porównaniu do porównania pojedynczej liczby całkowitej porównanie pojedynczej liczby całkowitej jest szybsze. Zamiast zmniejszać, możesz mieć wskaźnik do adresu początkowego w pamięci i wskaźnik do adresu końcowego w pamięci. Następnie zwiększaj wskaźnik początkowy, aż będzie on równy wskaźnikowi końcowemu. To wyeliminuje dodatkową operację przesunięcia pamięci w kodzie asemblera, tym samym zapewniając znacznie większą wydajność.
Jack Giffin

5

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”.


5

Jeśli lepszym sprzętem jest opcja, zdecydowanie skorzystaj z niej. Inaczej

  • Sprawdź, czy korzystasz z najlepszych opcji kompilatora i linkera.
  • Jeśli rutynowy punkt dostępu w innej bibliotece niż często dzwoniący, rozważ przeniesienie go lub klonowanie do modułu wywołującego. Eliminuje część narzutu wywołania i może poprawić trafienia w pamięci podręcznej (por. Jak AIX łączy statycznie strcpy () w osobno połączone obiekty współdzielone). To oczywiście może również zmniejszyć liczbę trafień w pamięci podręcznej, dlatego jeden środek.
  • Sprawdź, czy jest jakaś możliwość korzystania ze specjalnej wersji procedury hotspot. Minusem jest utrzymanie więcej niż jednej wersji.
  • Spójrz na asembler. Jeśli uważasz, że może być lepiej, zastanów się, dlaczego kompilator tego nie wymyślił i jak możesz pomóc kompilatorowi.
  • Zastanów się: czy naprawdę używasz najlepszego algorytmu? Czy to najlepszy algorytm dla twojego rozmiaru wejściowego?

Dodałbym do twojego pierwszego par .: nie zapomnij wyłączyć wszystkich informacji debugowania w opcjach kompilatora .
varnie

5

Sposób Google jest jedną z opcji „Buforuj go .. Jeśli to możliwe, nie dotykaj dysku”


5

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.


5

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.


5

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ń,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

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.


Dlatego podoba mi się to w OCaml, rzutowanie między typami liczbowymi musi być xplicit.
Gajusz

@ Gaius słuszny punkt - ale w wielu przypadkach zmiana języka nie jest realistycznym wyborem. Ponieważ C / C ++ są tak szeroko stosowane, przydatne może być ich zaostrzenie, nawet jeśli jest to specyficzne dla kompilatora.
ideasman42

4

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.


4

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 ...


2
Byłoby to prawidłowe podejście po niepowodzeniu wszystkich innych podejść lub gdyby konkretna funkcja systemu operacyjnego lub struktury była odpowiedzialna za znaczny spadek wydajności, ale poziom wiedzy specjalistycznej i kontroli niezbędny do jego realizacji może nie być dostępny dla każdego projektu.
Andrew Neely,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.