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 = 20
nie 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 / 2
się n >> 1
automatycznie, jeśli są kierowane do komputera głównego nurtu, ale jeśli jesteś kierowania wbudowanego urządzenia, możesz nie dostać tego luksusu.
Ponadto, % 2
i & 1
mają 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, % 2
kiedy 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
<-> double
konwersje 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;
}