Rozumiem, że czasami potrzebujesz właściwości, takich jak:
public int[] Transitions { get; set; }
lub:
[SerializeField] private int[] m_Transitions;
public int[] Transitions { get { return m_Transitions; } set { m_Transitions = value; } }
Ale mam wrażenie, że przy tworzeniu gier, chyba że masz powód, aby zrobić inaczej, wszystko powinno być tak szybkie, jak to możliwe. Mam wrażenie, że z rzeczy, które przeczytałem, Unity 3D nie ma pewności, aby uzyskać bezpośredni dostęp za pośrednictwem właściwości, więc użycie właściwości spowoduje wywołanie dodatkowej funkcji.
Czy mam prawo stale ignorować sugestie Visual Studio dotyczące nieużywania pól publicznych? (Należy pamiętać, że korzystamy z int Foo { get; private set; }
niego, gdy wykazanie zamierzonego użycia ma przewagę).
Wiem, że przedwczesna optymalizacja jest zła, ale jak powiedziałem, w rozwoju gier nie spowalniasz czegoś, gdy podobny wysiłek może przyspieszyć.
unity
performance
optimization
piojo
źródło
źródło
ArrayList
). Wynika to z faktu, że C nie ma generycznych, a ci programiści C prawdopodobnie łatwiej było wielokrotnie zaimplementować połączone listy niż robić to samo dlaArrayLists
algorytmu zmiany rozmiaru. Jeśli wpadniesz na dobrą optymalizację niskiego poziomu, obuduj ją!Odpowiedzi:
Niekoniecznie. Podobnie jak w oprogramowaniu aplikacyjnym, w grze znajduje się kod, który ma krytyczne znaczenie dla wydajności, a kod nie.
Jeśli kod jest wykonywany kilka tysięcy razy na ramkę, taka optymalizacja niskiego poziomu może mieć sens. (Chociaż możesz najpierw chcieć najpierw sprawdzić, czy rzeczywiście trzeba tak często go wywoływać. Najszybszym kodem jest kod, którego nie uruchamiasz)
Jeśli kod jest wykonywany raz na kilka sekund, to czytelność i łatwość konserwacji są znacznie ważniejsze niż wydajność.
Dzięki trywialnym właściwościom w stylu
int Foo { get; private set; }
możesz być pewien, że kompilator zoptymalizuje opakowanie właściwości. Ale:GetFoo()
/SetFoo(value)
metodę: sugerować, że za kulisami dzieje się znacznie więcej. (lub jeśli muszę być jeszcze bardziej pewien, że inni dostaną podpowiedź:CalculateFoo()
/SwitchFoo(value)
)źródło
Update
całkowite pominięcie pozwoli zaoszczędzić więcej czasu niżUpdate
szybkie.W razie wątpliwości skorzystaj z najlepszych praktyk.
Masz wątpliwości
To była łatwa odpowiedź. Rzeczywistość jest oczywiście bardziej złożona.
Po pierwsze, istnieje mit, że programowanie gier jest programowaniem o ultra wysokiej wydajności i wszystko musi być tak szybkie, jak to możliwe. Programowanie gier klasyfikuję jako programowanie zorientowane na wydajność . Deweloper musi być świadomy ograniczeń wydajności i pracować nad nimi.
Po drugie, istnieje mit, że sprawienie, by każda rzecz była tak szybka, jak to możliwe, powoduje, że program działa szybko. W rzeczywistości identyfikacja i optymalizacja wąskich gardeł sprawia, że program jest szybki i jest to o wiele łatwiejsze / tańsze / szybsze do zrobienia, jeśli kod jest łatwy w utrzymaniu i modyfikacji; bardzo zoptymalizowany kod jest trudny do utrzymania i modyfikacji. Wynika z tego, że aby pisać wyjątkowo zoptymalizowane programy, musisz używać ekstremalnej optymalizacji kodu tak często, jak to konieczne i tak rzadko, jak to możliwe.
Po trzecie, zaletą właściwości i akcesoriów jest to, że są odporne na zmiany, a tym samym ułatwiają konserwację i modyfikację kodu - czego potrzebujesz do pisania szybkich programów. Chcesz zgłosić wyjątek, gdy ktoś chce ustawić wartość na zero? Użyj setera. Chcesz wysłać powiadomienie za każdym razem, gdy zmienia się wartość? Użyj setera. Chcesz zalogować nową wartość? Użyj setera. Chcesz zrobić leniwą inicjalizację? Użyj gettera.
Po czwarte, osobiście nie przestrzegam najlepszych praktyk Microsoftu w tym przypadku. Jeśli potrzebuję nieruchomości z trywialnymi i publicznymi obiektami pobierającymi i ustawiającymi, po prostu używam pola i po prostu zmieniam go na właściwość, kiedy jest to konieczne. Jednak bardzo rzadko potrzebuję tak trywialnej właściwości - o wiele bardziej powszechne jest to, że chcę sprawdzić warunki brzegowe lub ustawić prywatność setera.
źródło
Środowisko wykonawcze .net bardzo łatwo wstawia proste właściwości i często tak jest. Ale to wstawianie jest zwykle wyłączone przez profilerów, dlatego łatwo jest zostać wprowadzonym w błąd i myśleć, że zmiana własności publicznej na publiczną przyspieszy oprogramowanie.
W kodzie wywoływanym przez inny kod, który nie zostanie ponownie skompilowany, gdy kod zostanie ponownie skompilowany, uzasadnione jest użycie właściwości, ponieważ zmiana z pola na właściwość jest przełomową zmianą. Ale dla większości kodów, poza tym, że mogę ustawić punkt przerwania w get / set, nigdy nie widziałem korzyści z używania prostej właściwości w porównaniu z polem publicznym.
Głównym powodem, dla którego korzystałem z właściwości przez 10 lat jako profesjonalny programista C # było powstrzymanie innych programistów od mówienia mi, że nie przestrzegam standardu kodowania. To był dobry kompromis, ponieważ prawie nigdy nie istniał dobry powód, aby nie korzystać z nieruchomości.
źródło
Struktury i puste pola ułatwią współdziałanie z niektórymi niezarządzanymi interfejsami API. Często okazuje się, że interfejs API niskiego poziomu chce uzyskać dostęp do wartości przez odniesienie, co jest dobre dla wydajności (ponieważ unikamy niepotrzebnej kopii). Korzystanie z właściwości jest przeszkodą w tym i często biblioteki otoki wykonują kopie dla ułatwienia użytkowania, a czasem dla bezpieczeństwa.
Z tego powodu często uzyskuje się lepszą wydajność dzięki typom wektorów i macierzy, które nie mają właściwości, ale puste pola.
Najlepsze praktyki nie powstają w próżni. Pomimo pewnego kultu ładunków, ogólnie dobre praktyki istnieją z dobrego powodu.
W tym przypadku mamy kilka:
Właściwość umożliwia zmianę implementacji bez zmiany kodu klienta (na poziomie binarnym można zmienić pole na właściwość bez zmiany kodu klienta na poziomie źródłowym, jednak po zmianie kompiluje się do czegoś innego) . Oznacza to, że przy użyciu właściwości od samego początku kod, który odwołuje się do twojego, nie będzie musiał zostać ponownie skompilowany, aby zmienić to, co ta właściwość robi wewnętrznie.
Jeśli nie wszystkie możliwe wartości pól twojego typu są poprawnymi stanami, nie chcesz narażać ich na kod klienta, który kod go modyfikuje. Dlatego jeśli niektóre kombinacje wartości są nieprawidłowe, chcesz zachować pola prywatne (lub wewnętrzne).
Mówiłem kod klienta. To oznacza kod, który woła do twojego. Jeśli nie tworzysz biblioteki (lub nawet tworzysz bibliotekę, ale używasz wewnętrznej zamiast publicznej), zwykle możesz uciec od niej i trochę dobrej dyscypliny. W takiej sytuacji najlepsza praktyka korzystania z właściwości ma na celu zapobieganie strzelaniu sobie w stopę. Co więcej, o wiele łatwiej jest wnioskować o kodzie, jeśli widzisz wszystkie miejsca, w których pole może się zmienić w jednym pliku, zamiast martwić się czymkolwiek, czy nie jest modyfikowane gdzie indziej. W rzeczywistości właściwości są również dobre do ustalenia punktów przerwania, gdy zastanawiasz się, co poszło nie tak.
Tak, warto zobaczyć, co dzieje się w branży. Czy jednak masz motywację, by przeciwstawić się najlepszym praktykom? czy po prostu przeciwstawiasz się najlepszym praktykom - utrudniając zrozumienie kodu - tylko dlatego, że zrobił to ktoś inny? Ach, nawiasem mówiąc, „inni to robią” to sposób na rozpoczęcie kultu ładunków.
Więc ... Czy twoja gra działa wolno? Lepiej poświęć czas na znalezienie wąskiego gardła i naprawienie go, zamiast spekulacji, co to może być. Możesz być pewny, że kompilator dokona wielu optymalizacji, dlatego istnieje szansa, że patrzysz na niewłaściwy problem.
Z drugiej strony, jeśli decydujesz, od czego zacząć, powinieneś martwić się najpierw o algorytmy i struktury danych, zamiast martwić się o mniejsze szczegóły, takie jak pola vs właściwości.
Wreszcie, czy zarabiasz coś, postępując wbrew najlepszym praktykom?
Czy w przypadku konkretnego przypadku (Unity i Mono dla Androida) Unity przyjmuje wartości przez odniesienie? Jeśli nie, skopiuje wartości i tak, nie ma tam wzrostu wydajności.
Jeśli tak, jeśli przekazujesz te dane do interfejsu API, który zajmuje ref. Czy ma sens upublicznienie pola, czy można sprawić, by typ mógł bezpośrednio wywoływać interfejs API?
Tak, oczywiście, mogą istnieć optymalizacje, które można wykonać, używając struktur z nagimi polami. Na przykład, uzyskujesz do nich dostęp za pomocą
wskaźnikówSpan<T>
lub podobnych. Są również kompaktowe w pamięci, co ułatwia ich serializację w celu przesłania przez sieć lub umieszczenia w stałej pamięci (i tak, to są kopie).A teraz, czy wybrałeś odpowiednie algorytmy i struktury, jeśli okażą się one wąskim gardłem, wtedy decydujesz, jaki jest najlepszy sposób, aby to naprawić ... które mogą być strukturami z nagimi polami lub nie. Będziesz mógł się tym martwić, jeśli i kiedy to się stanie. Tymczasem możesz martwić się ważniejszymi sprawami, takimi jak stworzenie dobrej lub fajnej gry, w którą warto grać.
źródło
:
Masz rację, wiedząc, że przedwczesna optymalizacja jest zła, ale to tylko połowa historii (patrz pełny cytat poniżej). Nawet przy tworzeniu gier nie wszystko musi być tak szybkie, jak to możliwe.
Najpierw spraw, by działało. Tylko wtedy zoptymalizuj bity, które tego potrzebują. Nie oznacza to, że pierwszy projekt może być bałaganiarski lub niepotrzebnie nieefektywny, ale optymalizacja nie jest priorytetem na tym etapie.
„ Prawdziwy problem polega na tym, że programiści spędzili zbyt wiele czasu martwiąc się o wydajność w niewłaściwych miejscach i w niewłaściwych momentach ; przedwczesna optymalizacja jest źródłem wszelkiego zła (a przynajmniej większości) w programowaniu”. Donald Knuth, 1974.
źródło
Jeśli chodzi o takie podstawowe rzeczy, optymalizator C # i tak to zrobi. Jeśli martwisz się o to (i martwisz się o to przez ponad 1% swojego kodu, robisz to źle ), atrybut został stworzony dla Ciebie. Ten atrybut
[MethodImpl(MethodImplOptions.AggressiveInlining)]
znacznie zwiększa szansę na wprowadzenie metody (metody pobierania i ustawiania są metodami).źródło
Ujawnienie elementów typu struktury raczej jako właściwości niż pól często przyniesie słabą wydajność i semantykę, szczególnie w przypadkach, w których struktury są faktycznie traktowane jako struktury, a nie jako „dziwaczne obiekty”.
Struktura w .NET jest zbiorem pól połączonych taśmą razem i które dla niektórych celów mogą być traktowane jako całość. .NET Framework został zaprojektowany tak, aby zezwalać na używanie struktur w taki sam sposób, jak obiektów klasowych. Czasami może to być wygodne.
Wiele rad dotyczących struktur opiera się na przekonaniu, że programiści będą chcieli używać struktur jako obiektów, a nie jako połączonych ze sobą zbiorów pól. Z drugiej strony istnieje wiele przypadków, w których użyteczne może być posiadanie czegoś, co zachowuje się jak skupiona razem grupa pól. Jeśli tego właśnie potrzebujesz, porady dotyczące platformy .NET dodadzą niepotrzebnej pracy zarówno programistom, jak i kompilatorom, zapewniając gorszą wydajność i semantykę niż bezpośrednie używanie struktur.
Najważniejsze zasady, o których należy pamiętać podczas korzystania ze struktur, to:
Nie przekazuj ani nie zwracaj struktur według wartości ani w inny sposób nie powoduj ich niepotrzebnego kopiowania.
Jeśli zasada 1 zostanie zachowana, duże struktury będą działać tak samo dobrze, jak małe.
Jeśli
Alphablob
jest strukturą zawierającą 26 publicznychint
pól nazwanych az, a nieruchomość getterab
która zwraca sumę swoicha
andb
pól, a następnie podanyAlphablob[] arr; List<Alphablob> list;
kodint foo = arr[0].ab + list[0].ab;
będzie trzeba czytać póla
ib
naarr[0]
, ale trzeba czytać wszystkie 26 polalist[0]
, mimo że będzie zignoruj wszystkie oprócz dwóch. Jeśli ktoś chciałby mieć ogólną kolekcję podobną do listy, która mogłaby wydajnie współpracować z podobną strukturąalphaBlob
, należy zastąpić indeksowany getter metodą:który wtedy by się przywołał
proc(ref backingArray[index], ref param);
. Biorąc pod uwagę taki zbiór, jeśli jeden zastępujesum = myCollection[0].ab;
zuniknęłoby to konieczności kopiowania części
alphaBlob
, które nie będą używane w module pobierania właściwości. Ponieważ przekazany delegat nie ma dostępu do niczego poza jegoref
parametrami, kompilator może przekazać statycznego delegata.Niestety, .NET Framework nie definiuje żadnego rodzaju delegatów potrzebnych do poprawnego działania tej funkcji, a składnia jest w pewnym sensie bałaganem. Z drugiej strony takie podejście pozwala uniknąć niepotrzebnego kopiowania struktur, co z kolei sprawi, że praktyczne będzie wykonywanie czynności w miejscu na dużych strukturach przechowywanych w tablicach, co jest najbardziej wydajnym sposobem dostępu do pamięci.
źródło
list
pobierający właściwościab
są wbudowane, może być możliwe, że JITter rozpozna tylko członkówa
ib
są potrzebne, ale nie jestem świadomy, że jakakolwiek wersja JITtera faktycznie robi takie rzeczy.