Vasto_Lorde 331 Posted June 29 Cześć! W trakcie próby optymalizacji naszego kodu, skracamy go, staramy się lepiej zarządzać pamięcią, ucinamy maksymalną liczbę graczy na serwerze w pętlach przebiegających po liście klientów czy pobieramy cvary tylko na początku mapy a nie "na żywo". Ale bardzo często zapominamy o tym, że możemy w prosty sposób zoptymalizować nasze warunki IF. Jak? Różne języki programowania mają różne sposoby na sprawdzanie końcowej wartości boolean w warunkach. W Sourcemod'zie występuje rodzaj sprawdzania o nazwie "Minimalna Ewaluacja". W prostych słowach oznacza to, że jeśli mamy warunek IF w którym znajduje się więcej niż jedno wyrażenie do porównania, to program będzie sprawdzał je od lewej do prawej. Ale to nie koniec. Najlepiej jeśli wyjaśnię to na przykładach: if (warunek1 && warunek2) { /* ...kod... */ } W tym warunku możemy mieć 4 przypadki: warunek1 == true oraz warunek2 == true (IF uznaje że całościowo jego wartość to true) warunek1 == true oraz warunek2 == false (IF uznaje że całościowo jego wartość to false) warunek1 == false oraz warunek2 == true (IF uznaje że całościowo jego wartość to false) warunek1 == false oraz warunek2 == false (IF uznaje że całościowo jego wartość to false) W przypadku gdy warunek1 to true, program musi sprawdzić jeszcze warunek2 by móc określić jaką w sumie ma wartość. Więc standardowo wykonają się dwa sprawdzenia i nie ma tutaj żadnej magii. Co innego dzieje się gdy pierwszy z warunków wynosi false. Jak wiemy (albo możemy się dowiedzieć czytając poradnik MAGNET'a o operacjach bitowychHej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!) operacja && (inaczej AND, "oraz" lub koniunkcja) jest true tylko i wyłącznie jeśli oba warunki są true. Więc jeśli warunek1 jest fałszem, Minimalna Ewaluacja w Sourcemod'zie sprawia, że drugiego warunku już nie sprawdzamy! Ponieważ nie ma znaczenia jaką wartość ma warunek2, jeśli warunek1 jest false to cały IF będzie przyjmował wartośc false. Inny przykład: if (warunek1 || warunek2) { /* ...kod... */ } Tutaj posługujemy się || (OR, "lub" albo alternatywa). Również możemy mieć 4 przypadki: warunek1 == true oraz warunek2 == true (IF uznaje że całościowo jego wartość to true) warunek1 == true oraz warunek2 == false (IF uznaje że całościowo jego wartość to true) warunek1 == false oraz warunek2 == true (IF uznaje że całościowo jego wartość to true) warunek1 == false oraz warunek2 == false (IF uznaje że całościowo jego wartość to false) Sytuacja jest prawie identyczna. Tyle, że jeśli warunek1 wynosi false, program będzie musiał sprawdzić drugą wartość. A jeśli warunek1 wynosi true, to program nie musi sprawdzać warunek2, bo niezależnie od wartości w warunek2, jeśli warunek1 == true, to cały IF zawsze będzie true.Przykład z życia Najbardziej popularną funkcją która jest jednym wielkim warunkiem jest IsValidClient, szeroko używana prawie w każdym pluginie, nawet jeśli nie w zwartej funkcyjnej formie, to jako sam warunek. Jej zadanie to sprawdzenie, czy klient jest "poprawny" czyli czy jest na serwerze, gra i jest człowiekiem (nie jest botem). Wygląda ona tak: stock bool IsValidClient(int client) {//Rozłożyłem warunek na parę linii żeby można było chwilowo dobrze go odczytać, nie róbcie tak z własnymi IFami ;) return ( 1 <= client <= MaxClients && IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && !IsClientSourceTV(client) ); } Pierwszy warunek: 1 <= client <= MaxClients Jest najważniejszy i "najmocniejszy". Dlaczego? Ponieważ wyrzuca nam największą ilość nieprawidłowych client'ów. Dlaczego? Byty (entity) na których możemy wywołać tę funkcję (w tym gracze) wahają swoje ID od 0 do teoretycznej nieskończoności (chociaż tak, jest limit bytów na mapie). Więc od razu, prostym porównaniem dwóch liczb (zauważmy, że nie pobieramy tutaj niczego) dla bardzo dużej ilości ID zamiast sprawdzać wszystkie 5 warunków, sprawdzamy tylko 1. Tylko maksymalnie 64 ID mogą przejść do sprawdzenia następnego warunku: IsClientConnected(client) && IsClientInGame(client) && Te dwa warunki omówimy razem ponieważ są bardzo podobne. Connected sprawdza, czy gracz łączy się lub jest już połączony z serwerem. InGame sprawdza, czy jest już w grze. W teorii, InGame może wyeliminować nam więcej nieprawidłowych ID (ponieważ jeśli gracz już jest w grze to na pewno jest połączony, więc nie musimy sprawdzać już czy jest połączony) ale niestety jeśli gracz nie jest połączony z serwerem, to InGame wywala nam error w konsoli. Dzięki tym warunkom, eliminujemy załóżmy połowę nieprawidłowych ID jeśli serwer jest tylko w połowie zapełniony. Następne warunki eliminują jeszcze mniej przypadków: !IsFakeClient(client) && !IsClientSourceTV(client) IsFakeClient sprawdza, czy klient nie jest botem. Może nam to wyeliminować parę nieprawidłowych ID, chociaż 90% serwerów nie używa botów. No i ostatni warunek sprawdzający czy ID jest GOTV, może nam wyeliminować maksymalnie jeden przypadek. Dlatego znajduje się na końcu. Przykład z życia 2 W niektórych przypadkach, możemy jeszcze bardziej zoptymalizować nawet tak dobrze zrobiony warunek jak IsValidClient. Na myśl przychodzi mi konkretnie Nowy Cod by LinuxHej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami! oraz jego klasy. W praktycznie każdej klasie codmod znajduje się zmienna "ma_klase[client]", która określa czy dany klient ma daną klasę i czy może korzystać z jej umiejętności. Oczywiście będziemy również tam używali IsValidClient. Ale inaczej! Mamy kod: public Action OnTakeDamage(client, int &attacker, int &inflictor, float &damage, int &damagetype) { if(!IsValidClient(attacker) || !IsValidClient(client) || GetClientTeam(client) == GetClientTeam(attacker)) return Plugin_Continue; if (ma_klase[attacker]) { damage *= 1.5; return Plugin_Changed; } return Plugin_Continue; } Ten kod, który zaraz będziemy optymalizować na pierwszy rzut oka nie ma żadnych wad. Standardowo sprawdzamy czy klienci związani z akcją są na serwerze oraz czy nie są w tej samej drużynie, potem sprawdzamy czy atakujący posiada odpowiednią klasę, jeśli tak, to powiększamy zadane przez niego obrażenia o 50%. Przedstawiam bardziej zoptymalizowaną wersję: public Action OnTakeDamage(client, int &attacker, int &inflictor, float &damage, int &damagetype) { if(1 <= attacker <= MaxClients && ma_klase[attacker] && !IsFakeClient(attacker) && !IsClientSourceTV(attacker)) { if(!IsValidClient(client) || GetClientTeam(client) == GetClientTeam(attacker)) return Plugin_Continue; damage *= 1.5; return Plugin_Changed; } return Plugin_Continue; } 1. Zamiast korzystać ze standardowego IsValidClient, modyfikujemy tę funkcje tak, by zaraz po sprawdzeniu czy ID klienta mieści się w przedziale 1-64 sprawdzało się czy gracz ma klasę. Dlaczego? W trybie cod, jest dużo klas, minimalnie 7 jeśli nie 30. W najgorszym przypadku (7 klas) jeśli wybór klas przez graczy jest równomierny, przy pełnym serwerze 64 slotowym eliminujemy aż 64/7 ~ = 90% przypadków! Ten procent zwiększa się im klasa jest mniej popularna/im więcej klas jest na serwerze (im mniej graczy gra daną klasą w danym momencie tym więcej przypadków ten IF wyeliminuje). 2. Ponadto ten warunek jest "naturalny", nie pobiera żadnych danych tylko działa na zapisanych już w pluginie wartościach. 3. Rezygnujemy kompletnie z IsClientInGame oraz IsClientConnected. Dlaczego? Ponieważ jeśli jakikolwiek ID gracza atakującego (1 <= attacker <= MaxClients) oraz posiadającego klasę zadało jakiekolwiek obrażenia (jesteśmy przypominam w OnTakeDamage, funkcji która wywołuje się tylko jeśli ktoś otrzyma obrażenia) to musi być ten gracz podłączony do serwera! Podobnie moglibyśmy zrobić z IsValidClient sprawdzającego ID client'a. Jeśli ID otrzymuje obrażenia i ma ID klienta (w przedziale 1-64) to na pewno jest żywy i połączony z serwerem Bibliografia https://forums.alliedmods.net/showthread.php?t=313306 https://forums.alliedmods.net/showthread.php?t=188033 https://en.wikipedia.org/wiki/Short-circuit_evaluation 3 Quote Share this post Link to post Share on other sites