Jakich prostych technik używasz do poprawy wydajności?

21

Mówię o tym, w jaki sposób piszemy proste procedury w celu poprawy wydajności bez utrudniania czytania kodu ... na przykład, jest to typowe, czego się nauczyliśmy:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

Ale zwykle robię to, gdy a foreachnie ma zastosowania:

for(int i = 0, j = collection.length(); i < j; i++ ){
   // stuff here
}

Myślę, że jest to lepsze podejście, ponieważ wywoła tę lengthmetodę tylko raz ... moja dziewczyna mówi, że jest to zagadkowe. Czy jest jakaś inna prosta sztuczka, której używasz w swoich projektach?

Cristian
źródło
34
Daj +1 tylko za to, że masz dziewczynę, która powie ci, kiedy twój kod nie jest jasny.
Kristo
76
Po prostu piszesz to, żeby powiedzieć nam, że masz dziewczynę.
Josh K
11
@Christian: Nie zapominaj, że istnieją optymalizacje kompilatora, które mogą to dla ciebie zrobić, więc możesz mieć wpływ tylko na czytelność i wcale nie wpływać na wydajność; przedwczesna optymalizacja jest źródłem wszelkiego zła ... Staraj się unikać więcej niż jednej deklaracji lub zadania w tym samym wierszu, nie każ ludziom czytać tego dwa razy ... Powinieneś użyć normalnego sposobu (twój pierwszy przykład) lub umieścić druga deklaracja poza pętlą for (chociaż to również zmniejsza czytelność, ponieważ trzeba by było przeczytać ponownie, aby zobaczyć, co oznacza j).
Tamara Wijsman
5
@TomWij: Prawidłowy (i kompletny) cytat: „Powinniśmy zapomnieć o małej wydajności, powiedzmy w około 97% przypadków: przedwczesna optymalizacja jest źródłem wszelkiego zła. Jednak nie powinniśmy tracić naszych możliwości w tak krytycznych 3%. „
Robert Harvey
3
@tomwij: Jeśli wydajesz trzy procent, z definicji powinieneś robić to w kodzie krytycznym czasowo, a nie marnować czasu na pozostałe 97%.
Robert Harvey

Odpowiedzi:

28

wstaw wykład przedwczesny-dyskusja-jest-źródłem-złego

To powiedziawszy, oto kilka nawyków, które wdrożyłem, aby uniknąć niepotrzebnej wydajności, aw niektórych przypadkach uczynić mój kod prostszym i bardziej poprawnym.

To nie jest omówienie ogólnych zasad, ale pewnych rzeczy, o których należy pamiętać, aby uniknąć wprowadzania niepotrzebnych nieefektywności do kodu.

Poznaj swoje duże-O

Powinno to prawdopodobnie zostać włączone do długiej dyskusji powyżej. Powszechnie wiadomo, że pętla wewnątrz pętli, w której pętla wewnętrzna powtarza obliczenia, będzie wolniejsza. Na przykład:

for (i = 0; i < strlen(str); i++) {
    ...
}

Zajmie to strasznie dużo czasu, jeśli łańcuch będzie naprawdę długi, ponieważ długość jest ponownie obliczana przy każdej iteracji pętli. Zauważ, że GCC faktycznie optymalizuje ten przypadek, ponieważ strlen()jest oznaczony jako czysta funkcja.

Podczas sortowania miliona 32-bitowych liczb całkowitych sortowanie bąbelkowe byłoby niewłaściwą drogą . Ogólnie rzecz biorąc, sortowanie może odbywać się w czasie O (n * log n) (lub lepiej, w przypadku sortowania Radix), więc jeśli nie wiesz, że twoje dane będą małe, poszukaj algorytmu, który jest co najmniej O (n * log n).

Podobnie, mając do czynienia z bazami danych, należy pamiętać o indeksach. Jeśli SELECT * FROM people WHERE age = 20nie masz indeksu osób (wiek), będzie to wymagać sekwencyjnego skanowania O (n) zamiast znacznie szybszego skanowania indeksu O (log n).

Hierarchia arytmetyczna liczb całkowitych

Podczas programowania w C należy pamiętać, że niektóre operacje arytmetyczne są droższe niż inne. W przypadku liczb całkowitych hierarchia wygląda mniej więcej tak (najpierw najtańsze):

  • + - ~ & | ^
  • << >>
  • *
  • /

To prawda, kompilator zwykle Optymalizacja takie rzeczy n / 2się n >> 1automatycznie, jeśli są kierowane do komputera głównego nurtu, ale jeśli jesteś kierowania wbudowanego urządzenia, możesz nie dostać tego luksusu.

Ponadto, % 2i & 1mają inną semantykę. Podział i moduł zwykle zaokrąglają się do zera, ale jego implementacja jest zdefiniowana. Dobry ol ' >>i &zawsze krąży w kierunku ujemnej nieskończoności, co (moim zdaniem) ma znacznie większy sens. Na przykład na moim komputerze:

printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1

Dlatego używaj tego, co ma sens. Nie myśl, że jesteś dobrym chłopcem, używając, % 2kiedy pierwotnie zamierzałeś pisać & 1.

Drogie operacje zmiennoprzecinkowe

Unikaj ciężkich operacji zmiennoprzecinkowych jak pow()i log()w kodzie, który tak naprawdę nie potrzebują, zwłaszcza gdy mamy do czynienia z liczbami całkowitymi. Weźmy na przykład czytanie numeru:

int parseInt(const char *str)
{
    const char *p;
    int         digits;
    int         number;
    int         position;

    // Count the number of digits
    for (p = str; isdigit(*p); p++)
        {}
    digits = p - str;

    // Sum the digits, multiplying them by their respective power of 10.
    number = 0;
    position = digits - 1;
    for (p = str; isdigit(*p); p++, position--)
        number += (*p - '0') * pow(10, position);

    return number;
}

To użycie pow()(i int<-> doublekonwersje potrzebne do jego użycia) jest nie tylko drogie, ale stwarza szansę na utratę precyzji (nawiasem mówiąc, powyższy kod nie ma problemów z precyzją). Właśnie dlatego wzdrygam się, gdy widzę, że tego typu funkcje są używane w kontekście niematematycznym.

Zauważ też, że poniższy „sprytny” algorytm, który mnoży przez 10 przy każdej iteracji, jest w rzeczywistości bardziej zwięzły niż powyższy kod:

int parseInt(const char *str)
{
    const char *p;
    int         number;

    number = 0;
    for (p = str; isdigit(*p); p++) {
        number *= 10;
        number += *p - '0';
    }

    return number;
}
Joey Adams
źródło
Bardzo dokładna odpowiedź.
Paddyslacker,
1
Uwaga: przedwczesna dyskusja na temat optymalizacji nie dotyczy kodu śmieci. Zawsze powinieneś zawsze używać implementacji działającej dobrze.
Zauważ, że GCC faktycznie optymalizuje ten przypadek, ponieważ strlen () jest oznaczony jako czysta funkcja. Myślę, że masz na myśli, że jest to stała funkcja, a nie czysta.
Andy Lester,
@Andy Lester: Właściwie miałem na myśli czysty. W dokumentacji GCC stwierdza, że ​​const jest nieco bardziej rygorystyczny niż czysty, ponieważ funkcja const nie może odczytać pamięci globalnej. strlen()sprawdza ciąg wskazany przez argument wskaźnika, co oznacza, że ​​nie może być const. Co więcej, strlen()jest rzeczywiście oznaczony jako czysty w glibcstring.h
Joey Adams
Masz rację, mój błąd i powinienem był dwukrotnie sprawdzić. Pracuję na funkcje adnotacji projektu Parrot jako albo purealbo consti nawet udokumentowanych go w pliku nagłówkowym powodu subtelnej różnicy między nimi. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.html
Andy Lester
13

Z twojego pytania i wątku komentarza wygląda to tak, jakbyś „pomyślał”, że ta zmiana kodu poprawia wydajność, ale tak naprawdę nie wiesz, czy tak się dzieje, czy nie.

Jestem fanem filozofii Kent Beck :

„Spraw, by działało, poprawiło, uczyniło to szybko”.

Moją techniką poprawiania wydajności kodu jest najpierw uzyskanie kodu pomyślnie przechodzącego testy jednostkowe, a następnie dobrze uwzględnione, a następnie (szczególnie w przypadku operacji w pętli) napisanie testu jednostkowego, który sprawdza wydajność, a następnie refaktoryzacja kodu lub wymyślenie innego algorytmu, jeśli ten „ wybrana opcja nie działa zgodnie z oczekiwaniami.

Na przykład, aby przetestować szybkość za pomocą kodu .NET, używam atrybutu Limit czasu NUnit do pisania twierdzeń, że wywołanie określonej metody zostanie wykonane w określonym czasie.

Używając czegoś takiego jak atrybut limitu czasu NUnit w podanym przykładzie kodu (i dużej liczbie iteracji dla pętli), możesz faktycznie udowodnić, czy twoje „ulepszenie” kodu naprawdę pomogło w wykonaniu tej pętli.

Jedno zastrzeżenie: Chociaż jest to skuteczne na poziomie „mikro”, z pewnością nie jest to jedyny sposób testowania wydajności i nie bierze pod uwagę problemów, które mogą wystąpić na poziomie „makro” - ale to dobry początek.

Paddyslacker
źródło
2
Chociaż jestem wielkim zwolennikiem profilowania, uważam również, że mądrze jest pamiętać o rodzajach wskazówek, których Cristian szuka. Zawsze wybiorę najszybszą z dwóch równie czytelnych metod. Zmuszenie do post-dojrzałej optymalizacji nie jest przyjemnością.
AShelly
Testy jednostkowe niekoniecznie są potrzebne, ale zawsze warto poświęcić 20 minut na sprawdzenie, czy jakiś mit dotyczący wydajności jest prawdziwy, czy nie, szczególnie, że odpowiedź często zależy od kompilatora i stanu opcji -O i -g (lub debugowania / Wydanie w przypadku VS).
mbq
+1 Ta odpowiedź uzupełnia mój powiązany komentarz do samego pytania.
Tamara Wijsman,
1
@AShelly: jeśli mówimy o prostych przeformułowaniach składni pętli, zmiana tego po fakcie jest bardzo łatwa. Również to, co uważasz za równie czytelne, może nie być takie dla innych programistów. Najlepiej stosować jak najwięcej „standardową” składnię i zmieniać ją tylko wtedy, gdy okaże się to konieczne.
Joeri Sebrechts
@ Czy na pewno możesz wymyślić dwie równie czytelne metody i wybierzesz mniej wydajną, po prostu nie wykonujesz swojej pracy? Czy ktoś by to zrobił?
glenatron
11

Pamiętaj, że twój kompilator może się obrócić:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

w:

int j = collection.length();
for(int i = 0; i < j; i++ ){
   // stuff here
}

lub coś podobnego, jeśli collectionnie uległo zmianie w pętli.

Jeśli ten kod znajduje się w części krytycznej czasowo Twojej aplikacji, warto dowiedzieć się, czy tak jest, czy nie - a nawet czy możesz zmienić opcje kompilatora, aby to zrobić.

Pozwoli to zachować czytelność kodu (ponieważ jest to pierwsze, czego większość ludzi się spodziewa), a jednocześnie zyska te dodatkowe cykle maszyny. Następnie możesz skoncentrować się na innych obszarach, w których kompilator nie może ci pomóc.

Na marginesie: jeśli zmienisz collectionwewnątrz pętli poprzez dodanie lub usunięcie elementów (tak, wiem, że to zły pomysł, ale tak się dzieje), wtedy twój drugi przykład albo nie zapętli wszystkich elementów, albo spróbuje uzyskać dostęp do przeszłości koniec tablicy.

ChrisF
źródło
1
Dlaczego nie zrobić tego wprost?
3
W niektórych językach, które sprawdzają granice, SPOWOLNIJ swój kod, jeśli zrobisz to jawnie. Dzięki pętli do collection.length kompilator przenosi go za Ciebie i pomija sprawdzanie granic. Dzięki zapętleniu do stałej z dowolnego miejsca w aplikacji będziesz mieć kontrolę granic dla każdej iteracji. Dlatego ważne jest, aby mierzyć - intuicja dotycząca wydajności prawie nigdy nie jest właściwa.
Kate Gregory
1
Dlatego powiedziałem „warto się dowiedzieć”.
ChrisF
Skąd kompilator C # może wiedzieć, że collection.length () nie modyfikuje kolekcji, tak jak robi to stack.pop ()? Myślę, że lepiej byłoby sprawdzić IL niż założyć, że kompilator to zoptymalizuje. W C ++ możesz oznaczyć metodę jako const („nie zmienia obiektu”), więc kompilator może bezpiecznie dokonać tej optymalizacji.
JBRWilkinson
1
Optymalizatory @JBRW, które to robią, są również świadome metod metod kolekcji, które są ok-let-call-it-constness-chociaż-to-nie-C ++. W końcu możesz sprawdzić tylko, czy zauważysz, że coś jest kolekcją i wiesz, jak uzyskać jego długość.
Kate Gregory,
9

Tego rodzaju optymalizacja zwykle nie jest zalecana. Kompilacji można łatwo dokonać przy pomocy tej optymalizacji, pracujesz z językiem programowania wyższego poziomu zamiast asemblera, więc myśl na tym samym poziomie.

tactoth
źródło
1
Daj jej książkę o programowaniu;)
Joeri Sebrechts
1
+1, ponieważ większość naszych dziewczyn jest bardziej zainteresowana Lady Gaga niż klarownością kodu.
haploid
Czy możesz wyjaśnić, dlaczego nie jest to zalecane?
Macneil
@macneil dobrze ... ta sztuczka sprawia, że ​​kody nie są tak powszechne i całkowicie nie działają, ten kawałek optymalizacji powinien zostać wykonany przez kompilator.
tactoth
@macneil, jeśli pracujesz w języku wyższego poziomu, pomyśl na tym samym poziomie.
tactoth
3

To może nie dotyczyć tak dużo kodowania ogólnego przeznaczenia, ale obecnie głównie zajmuję się programowaniem wbudowanym. Mamy konkretny procesor docelowy (który nie przyspieszy - do czasu wycofania systemu za ponad 20 lat wyda się dziwnie przestarzały) i bardzo restrykcyjne terminy dla dużej części kodu. Procesor, podobnie jak wszystkie procesory, ma pewne dziwactwa dotyczące tego, które operacje są szybkie lub wolne.

Stosujemy technikę zapewniającą, że generujemy najbardziej wydajny kod, przy jednoczesnym zachowaniu czytelności dla całego zespołu. W miejscach, w których najbardziej naturalna konstrukcja języka nie generuje najbardziej wydajnego kodu, stworzyliśmy makra, które zapewniają stosowanie optymalnego kodu. Jeśli wykonamy kolejny projekt dla innego procesora, możemy zaktualizować makra dla optymalnej metody na tym procesorze.

Jako konkretny przykład dla naszego obecnego procesora, gałęzie opróżniają rurociąg, blokując procesor na 8 cykli. Kompilator pobiera następujący kod:

 bool isReady = (value > TriggerLevel);

i zamienia go w ekwiwalent zestawu

isReady = 0
if (value > TriggerLevel)
{
  isReady = 1;
}

To zajmie albo 3 cykle, albo 10, jeśli przeskoczy isReady=1;. Ale procesor ma maxinstrukcję pojedynczego cyklu , więc znacznie lepiej jest napisać kod, aby wygenerować tę sekwencję, która gwarantuje, że zawsze zajmie 3 cykle:

diff = value-TriggerLevel;
diff = max(diff, 0);
isReady = min(1,diff);

Oczywiście intencja tutaj jest mniej jasna niż oryginał. Stworzyliśmy więc makro, którego używamy, gdy chcemy logiczne porównanie Wielkiego Niż:

#define BOOL_GT(a,b) min(max((a)-(b),0),1)

//isReady = value > TriggerLevel;
isReady = BOOL_GT(value, TriggerLevel);

Możemy zrobić podobne rzeczy dla innych porównań. Dla osoby z zewnątrz kod jest nieco mniej czytelny niż gdybyśmy użyli tylko naturalnej konstrukcji. Jednak szybko staje się to jasne po spędzeniu trochę czasu na pracy z kodem i jest znacznie lepsze niż pozwalanie każdemu programistowi na eksperymentowanie z własnymi technikami optymalizacji.

AShelly
źródło
3

Cóż, pierwszą radą byłoby uniknięcie takich przedwczesnych optymalizacji, dopóki nie będziesz dokładnie wiedział, co dzieje się z kodem, abyś był pewien, że faktycznie przyspieszasz go, a nie wolniej.

Na przykład w języku C # kompilator zoptymalizuje kod, jeśli zapętlasz długość tablicy, ponieważ wie, że nie musi on zmieniać zakresu, sprawdzaj indeks podczas uzyskiwania dostępu do tablicy. Jeśli spróbujesz go zoptymalizować, umieszczając długość tablicy w zmiennej, przerwiesz połączenie między pętlą a tablicą i faktycznie spowolnisz kod.

Jeśli zamierzasz dokonać mikrooptymalizacji, powinieneś ograniczyć się do rzeczy, o których wiadomo, że wykorzystują wiele zasobów. Jeśli tylko nieznacznie zwiększysz wydajność, powinieneś użyć najbardziej czytelnego i łatwego do utrzymania kodu. Jak praca komputera zmienia się w czasie, więc coś, co odkryłeś, jest teraz nieco szybsze, może nie zostać w ten sposób.

Guffa
źródło
3

Mam bardzo prostą technikę.

  1. Sprawiam, że mój kod działa.
  2. Testuję to pod kątem prędkości.
  3. Jeśli jest szybki, wrócę do kroku 1, aby uzyskać inną funkcję. Jeśli jest wolny, profiluję go, aby znaleźć wąskie gardło.
  4. Naprawiam wąskie gardło. Wróć do kroku 1.

Wiele razy oszczędza się czas na obejście tego procesu, ale ogólnie będziesz wiedział, czy tak jest. W razie wątpliwości trzymam się tego domyślnie.

Jason Baker
źródło
2

Skorzystaj ze zwarcia:

if(someVar || SomeMethod())

kodowanie zajmuje tyle samo czasu i jest tak samo czytelne jak:

if(someMethod() || someVar)

ale z czasem będzie szybciej oceniać.

realworldcoder
źródło
1

Poczekaj sześć miesięcy, każ szefowi kupić wszystkim nowe komputery. Poważnie. W dłuższej perspektywie czas programisty jest znacznie droższy niż sprzęt. Komputery o wysokiej wydajności pozwalają programistom pisać kod w prosty sposób, nie martwiąc się o szybkość.

Kristo
źródło
6
Er ... Co z wydajnością, którą widzą Twoi klienci? Czy jesteś wystarczająco bogaty, aby kupić dla nich nowe komputery?
Robert Harvey
2
I prawie uderzyliśmy w ścianę wydajności; obliczenia wielordzeniowe to jedyne wyjście, ale czekanie nie spowoduje, że twoje programy go wykorzystają.
mbq
+1 Ta odpowiedź uzupełnia mój powiązany komentarz do samego pytania.
Tamara Wijsman
3
Żaden czas programowania nie jest droższy niż sprzęt, gdy masz tysiące lub miliony użytkowników. Czas programisty NIE jest ważniejszy niż czas użytkownika. Przeprowadź go sobie jak najszybciej.
HLGEM
1
Nabierz dobrych nawyków, wtedy programista nie zajmuje czasu, ponieważ robisz to cały czas.
Dominique McDonnell,
1

Staraj się nie optymalizować zbyt wiele z wyprzedzeniem, a kiedy będziesz optymalizować, mniej martw się o czytelność.

Niewiele nienawidzę bardziej niż niepotrzebnej złożoności, ale kiedy napotykasz złożoną sytuację, często wymagane jest złożone rozwiązanie.

Jeśli piszesz kod w najbardziej oczywisty sposób, zrób komentarz wyjaśniający, dlaczego został zmieniony podczas wprowadzania złożonej zmiany.

Jednak konkretnie w twoim znaczeniu, często okazuje się, że robienie logicznego przeciwieństwa domyślnego podejścia czasami pomaga:

for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}

może zostać

for(int i = collection.length(); i > 0; i-=1 ){
// stuff here
}

W wielu językach, o ile wprowadzisz odpowiednie poprawki do części „rzeczy” i jest ona nadal czytelna. Po prostu nie podchodzi do problemu w taki sposób, w jaki większość ludzi pomyślałaby o zrobieniu go najpierw, ponieważ liczy się on wstecz.

na przykład w c #:

        string[] collection = {"a","b"};

        string result = "";

        for (int i = 0, j = collection.Count() - 1; i < j; i++)
        {
            result += collection[i] + "~";
        }

można również zapisać jako:

        for (int i = collection.Count() - 1; i > 0; i -= 1)
        {
            result = collection[i] + "~" + result;
        }

(i tak, powinieneś to zrobić za pomocą joysticka lub konstruktora ciągów, ale próbuję zrobić prosty przykład)

Istnieje wiele innych sztuczek, których można użyć, które nie są trudne do naśladowania, ale wiele z nich nie ma zastosowania we wszystkich językach, takich jak użycie środkowej części po lewej stronie zadania w starym VB, aby uniknąć kary za ponowne przypisanie łańcucha lub czytanie plików tekstowych w trybie binarnym w .net, aby ominąć karę buforowania, gdy plik jest zbyt duży do odczytu.

Jedynym innym bardzo ogólnym przypadkiem, jaki wymyślę, który miałby zastosowanie wszędzie, byłoby zastosowanie algebry boolowskiej do złożonych warunków warunkowych, aby spróbować przekształcić równanie w coś, co ma większą szansę na skorzystanie z warunku zwarcia lub przekształcenie kompleksu zestaw zagnieżdżonych instrukcji if-then lub case w równaniu w całości. Żadne z nich nie działa we wszystkich przypadkach, ale mogą być znaczną oszczędnością czasu.

Rachunek
źródło
jest to rozwiązanie, ale kompilator prawdopodobnie wypluje ostrzeżenia, ponieważ dla większości popularnych klas length () zwraca typ bez znaku
stijn
Ale przez odwrócenie indeksu sama iteracja może stać się bardziej złożona.
Tamara Wijsman,
@stijn Myślałem o c #, kiedy to napisałem, ale być może ta sugestia również mieści się w kategorii języka z tego powodu - patrz edycja ... @ToWij na pewno nie sądzę, że istnieje wiele, jeśli jakieś sugestie tego rodzaju które nie ryzykują tego. Jeśli twoje // rzeczy były pewnego rodzaju manipulowaniem stosami, może nie być nawet możliwe prawidłowe odwrócenie logiki, ale w wielu przypadkach jest to i nie jest zbyt mylące, jeśli zrobione ostrożnie w większości tych przypadków IMHO.
Bill
masz rację; w C ++ nadal wolałbym „normalną” pętlę, ale z wywołaniem length () wyjętym z iteracji (jak w const size_t len ​​= collection.length (); for (size_t i = 0; i <len; ++ i) {}) z dwóch powodów: uważam, że „normalna” pętla zliczania do przodu jest bardziej czytelna / zrozumiała (ale to prawdopodobnie tylko dlatego, że jest bardziej powszechna) i pobiera z niej wywołanie niezmiennej długości () pętli.
stijn
1
  1. Profil. Czy w ogóle mamy problem? Gdzie?
  2. W 90% przypadków, gdy jest to w jakiś sposób związane z IO, zastosuj buforowanie (a może uzyskaj więcej pamięci)
  3. Jeśli jest to związane z procesorem, zastosuj buforowanie
  4. Jeśli wydajność nadal stanowi problem, opuściliśmy sferę prostych technik - wykonaj matematykę.
Maglob
źródło
1

Używaj najlepszych dostępnych narzędzi - dobrego kompilatora, dobrego profilera, dobrych bibliotek. Skorzystaj z algorytmów, a najlepiej - użyj odpowiedniej biblioteki, aby zrobić to za Ciebie. Trywialne optymalizacje pętli to małe ziemniaki, a ponadto nie jesteś tak inteligentny jak kompilator optymalizujący.

Praca
źródło
1

Najprostszym dla mnie jest użycie stosu, gdy jest to możliwe, ilekroć wspólny wzorzec użycia przypadku pasuje do zakresu, powiedzmy, [0, 64), ale ma rzadkie przypadki, które nie mają małej górnej granicy.

Prosty przykład C (wcześniej):

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int* values = calloc(n, sizeof(int));

    // do stuff with values
    ...
    free(values);
}

Oraz po:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int values_mem[64] = {0}
    int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int));

    // do stuff with values
    ...
    if (values != values_mem)
        free(values);
}

Uogólniłem to w ten sposób, ponieważ tego rodzaju hotspoty często pojawiają się w profilowaniu:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    MemFast values_mem;
    int* values = mf_calloc(&values_mem, n, sizeof(int));

    // do stuff with values
    ...

    mf_free(&values_mem);
}

Powyższe używa stosu, gdy przydzielane dane są wystarczająco małe w tych 99,9% przypadkach, a stosu używa inaczej.

W C ++ uogólniłem to za pomocą zgodnej ze standardami małej sekwencji (podobnej do SmallVectorimplementacji tam dostępnych), która obraca się wokół tej samej koncepcji.

Nie jest to epicka optymalizacja (otrzymałem redukcje z, powiedzmy, 3 sekund, aby operacja zakończyła się do 1,8 sekundy), ale wymaga tak trywialnego wysiłku. Kiedy możesz obniżyć wartość z 3 do 1,8 sekundy, po prostu wprowadzając wiersz kodu i zmieniając dwa, to całkiem niezły huk jak na tak małą złotówkę.


źródło
0

Istnieje wiele zmian wydajności, które możesz wprowadzić podczas uzyskiwania dostępu do danych, które będą miały ogromny wpływ na twoją aplikację. Jeśli piszesz zapytania lub używasz ORM, aby uzyskać dostęp do bazy danych, musisz przeczytać kilka książek o optymalizacji wydajności dla używanego zaplecza bazy danych. Możliwe, że używasz znanych słabo działających technik. Nie ma powodu, aby to robić, oprócz ignorancji. To nie jest przedwczesna optymalizacja (przeklinam faceta, który to powiedział, ponieważ jest tak szeroko interpretowana, że ​​nigdy nie martwi się wydajnością), to dobry projekt.

Tylko krótka próbka ulepszeń wydajności dla SQL Server: Używaj odpowiednich indeksów, unikaj kursorów - używaj logiki opartej na zestawie, używaj klauzuli sargable gdzie, nie nakładaj widoków na wierzch widoków, nie zwracaj więcej danych niż potrzebujesz lub więcej kolumn, niż potrzebujesz, nie używaj skorelowanych podkwerend.

HLGEM
źródło
0

Jeśli jest to C ++, powinieneś ++iraczej przyzwyczaić się niż i++. ++inigdy nie będzie gorszy, oznacza to dokładnie to samo, co samodzielne oświadczenie, aw niektórych przypadkach może to być poprawa wydajności.

Nie warto zmieniać istniejącego kodu na wszelki wypadek, że to pomoże, ale dobrym nawykiem jest wchodzenie w to.

David Thornley
źródło
0

Mam trochę inne podejście do tego. Samo podążanie za wskazówkami nie zrobi wielkiej różnicy, ponieważ musisz popełnić kilka błędów, które następnie musisz naprawić, z których następnie musisz się nauczyć.

Błąd, który musisz popełnić, polega na zaprojektowaniu struktury danych tak, jak wszyscy. Oznacza to, że z nadmiarowymi danymi i wieloma warstwami abstrakcji, z właściwościami i powiadomieniami, które rozprzestrzeniają się w całej strukturze, starając się zachować spójność.

Następnie musisz przeprowadzić dostrajanie wydajności (profilowanie) i pokazać, że pod wieloma względami koszta cykli kosztują wiele warstw abstrakcji, a właściwości i powiadomienia rozprzestrzeniają się w całej strukturze, starając się zachować spójność.

Możesz być w stanie rozwiązać te problemy nieco bez większych zmian w kodzie.

Następnie, jeśli masz szczęście, możesz dowiedzieć się, że mniejsza struktura danych jest lepsza i że lepiej jest tolerować przejściową niespójność, niż starać się ściśle trzymać wiele rzeczy w zgodzie z falami wiadomości.

Sposób pisania pętli nie ma z tym nic wspólnego.

Mike Dunlavey
źródło