Skocz do zawartości

Rekomendowane odpowiedzi

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 bitowych
Hej! 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 Linux
Hej! 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

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dołącz do dyskusji

Możesz dodać zawartość już teraz a zarejestrować się później. Jeśli posiadasz już konto, zaloguj się aby dodać zawartość za jego pomocą.

Gość
Dodaj odpowiedź do tematu...

×   Wklejono zawartość z formatowaniem.   Usuń formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Odnośnik został automatycznie osadzony.   Przywróć wyświetlanie jako odnośnik

×   Przywrócono poprzednią zawartość.   Wyczyść edytor

×   Nie możesz bezpośrednio wkleić obrazków. Dodaj lub załącz obrazki z adresu URL.


×
×
  • Dodaj nową pozycję...