Czy przedwcześnie optymalizuję?

9

Obecnie jestem na etapie projektowania architektury opartej na komponentach w C ++.

Mój obecny projekt obejmuje wykorzystanie takich funkcji, jak:

  • std::vectors std::shared_ptrdo trzymania komponentów
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

Komponenty będą reprezentować dane i logikę różnych elementów potrzebnych w oprogramowaniu podobnym do gry, takim jak grafika, fizyka, sztuczna inteligencja, audio itp.

Wszędzie czytałem, że brakujące pamięci podręczne mają duży wpływ na wydajność, więc przeprowadziłem kilka testów, które doprowadziły mnie do wniosku, że może to spowolnić działanie aplikacji.

Nie byłem w stanie przetestować wyżej wymienionych funkcji językowych, ale w wielu miejscach mówi się, że kosztują one dużo i w miarę możliwości należy ich unikać.

Ponieważ jestem na etapie projektowania architektury i zostaną one uwzględnione w rdzeniu projektu, czy powinienem teraz spróbować znaleźć sposoby na ich uniknięcie, ponieważ będzie bardzo trudno zmienić to później, jeśli będzie wydajność problemy?

A może po prostu przyłapałem się na przedwczesnej optymalizacji?

Vaillancourt
źródło
3
Byłbym bardzo niechętny, aby zdecydować się na projekt, który bardzo utrudnił później zmianę, niezależnie od problemów z wydajnością. Unikaj tego, jeśli możesz. Istnieje wiele wzorów, które są zarówno elastyczne, jak i szybkie.
candied_orange 27.07.16
1
Bez znajomości szczegółów odpowiedź na to pytanie brzmi prawie zawsze: „TAK !!”.
Mawg mówi o przywróceniu Moniki
2
@Mawg „... Nie powinniśmy jednak tracić naszych szans w tak krytycznych 3%.” Ponieważ jest to rdzeń projektu, skąd mam wiedzieć, czy pracuję nad tymi 3%?
Vaillancourt
1
Doskonałe punkty, Alexandre (+1), i tak, wiem, że ostatnia połowa cytatu, o której prawie nigdy się nie wspomina :-) Ale wracając do mojego komentarza wcześniej (co znajduje odzwierciedlenie w otrzymanej odpowiedzi) , the answer to this question is almost always a resounding "YES !!". Nadal uważam, że lepiej jest najpierw uruchomić i zoptymalizować później, ale YMMV, każdy ma swoje zdanie, z których wszystkie są ważne i tylko OP może naprawdę odpowiedzieć na swoje - subiektywne - pytanie.
Mawg mówi o przywróceniu Moniki
1
@AlexandreVaillancourt Czytaj dalej artykuł Knutha (PDF, cytat pochodzi z prawej strony strony 268, strona 8 w czytniku PDF). „... mądrze będzie przyjrzeć się krytycznemu kodowi, ale dopiero po jego zidentyfikowaniu. Często błędem jest a priori osądzanie, które części programu są naprawdę krytyczne, ponieważ powszechne doświadczenie programiści, którzy używali narzędzi pomiarowych, nie potrafią zgadnąć. ” (Podkreślenie jego)
8bittree

Odpowiedzi:

26

Bez czytania niczego poza tytułem: Tak.

Po przeczytaniu tekstu: Tak. Chociaż prawdą jest, że mapy i wspólne wskaźniki itp. Nie działają dobrze pod względem pamięci podręcznej, z pewnością okaże się, że to, co chcesz z nich korzystać - o ile rozumiem - nie stanowi wąskiego gardła i nie będzie trzymane w efektywnie wykorzystuj pamięć podręczną niezależnie od struktury danych.

Napisz oprogramowanie, unikając najgłupszych błędów, a następnie przetestuj, a następnie znajdź wąskie gardła i zoptymalizuj!

Fwiw: https://xkcd.com/1691/

steffen
źródło
3
Zgoda. Najpierw upewnij się, że działa poprawnie, ponieważ nie będzie miało znaczenia, jak szybko nie działa. I zawsze pamiętaj, że najskuteczniejsze optymalizacje nie wymagają ulepszania kodu, wymagają znalezienia innego, bardziej wydajnego algorytmu.
Todd Knarr
10
Chciałbym wskazać, że pierwsza linia nie jest prawdziwa, ponieważ optymalizacja jest zawsze przedwczesna, ale raczej dlatego, że optymalizacja nie jest przedwczesna tylko wtedy, gdy wiesz, że jej potrzebujesz, w którym to przypadku nie pytasz o nią. Tak więc pierwsza linia jest prawdziwa tylko dlatego, że sam fakt, że zadajesz pytanie, czy optymalizacja jest przedwczesna, oznacza, że ​​nie masz pewności, czy potrzebujesz optymalizacji, co z definicji czyni ją przedwczesną. Uff
Jörg W Mittag
@ JörgWMittag: uzgodniony.
steffen
3

Nie znam C ++, ale ogólnie Zależy.

Nie trzeba przedwcześnie optymalizować izolowanych algorytmów, w których można łatwo zoptymalizować, jeśli chodzi o to.

Jednak musisz uzyskać ogólny projekt aplikacji, aby osiągnąć pożądane kluczowe wskaźniki wydajności.

Na przykład, jeśli chcesz zaprojektować aplikację do obsługi milionów żądań na sekundę, musisz pomyśleć o skalowalności aplikacji podczas jej projektowania, a nie o uruchomieniu aplikacji.

Nisko latający pelikan
źródło
3

Jeśli musisz zapytać, to tak. Przedwczesna optymalizacja oznacza optymalizację, zanim masz pewność, że występuje poważny problem z wydajnością.

JacquesB
źródło
1

ECS? Właściwie zasugeruję, że może to nie być przedwczesne, jeśli tak, aby przemyśleć stronę projektu zorientowaną na dane i porównać różne powtórzenia, ponieważ może to wpłynąć na projekty interfejsów , a ta ostatnia jest bardzo kosztowna, aby zmienić ją późno gra. Również ECS wymaga dużo pracy i przemyślenia z wyprzedzeniem i myślę, że warto poświęcić trochę tego czasu, aby upewnić się, że nie przyniesie ci to żalu wydajności na poziomie projektowym, biorąc pod uwagę, jak będzie w sercu twojego cały cholerny silnik. Ta część rzuca mi spojrzenie:

unordered_map<string,[yada]>

Nawet przy małych optymalizacjach ciągów kontener o zmiennej wielkości (łańcuchy) znajduje się w innym kontenerze o zmiennej wielkości (unordered_maps). W rzeczywistości, małe optymalizacje strunowe faktycznie mogła być równie szkodliwe jak pomocne w tym przypadku, jeśli stół jest bardzo rzadki, ponieważ małe optymalizacja ciąg oznaczałoby, że każdy niewykorzystany indeks tabeli mieszania będzie nadal korzystać z większej ilości pamięci dla optymalizacji SS ( sizeof(string)będzie być znacznie większy) do tego stopnia, że ​​całkowity narzut pamięci w tabeli skrótów może kosztować więcej niż cokolwiek, co jest w niej przechowywane, szczególnie jeśli jest to prosty komponent, taki jak element pozycji, oprócz ponoszenia większej ilości braków w pamięci podręcznej z ogromnym krokiem aby przejść od jednego wpisu w tabeli skrótów do następnego.

Zakładam, że ciąg jest jakimś kluczem, takim jak identyfikator komponentu. Jeśli tak, to już czyni rzeczy znacznie tańszymi:

unordered_map<int,[yada]>

... jeśli chcesz mieć korzyści z posiadania przyjaznych dla użytkownika nazw, których mogą używać skrypty, np. wtedy wewnętrzne łańcuchy mogą dać ci to, co najlepsze z obu światów.

To powiedziawszy, jeśli możesz odwzorować ciąg na stosunkowo niski zakres gęsto używanych wskaźników, możesz po prostu być w stanie to zrobić:

vector<[yada]> // the index and key become one and the same

Powodem, dla którego nie uważam tego przedwczesnego, jest to, że znowu może to wpłynąć na projekty interfejsów. Celem DOD nie powinna być próba wymyślenia najbardziej wydajnych reprezentacji danych możliwych do wyobrażenia za jednym razem IMO (które ogólnie powinny być osiągane iteracyjnie w razie potrzeby), ale przemyślenie ich na tyle, aby zaprojektować interfejsy do pracy z tym dane, które zapewniają wystarczającą swobodę oddechu do profilowania i optymalizacji bez kaskadowych zmian w projekcie.

Naiwnym przykładem jest oprogramowanie do przetwarzania wideo, które łączy cały swój kod z tym:

// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels, 
// 16-bit channels, 32-bit channels, grayscale, monochrome, 
// etc. pixels.
class IPixel
{
public:
    virtual ~IPixel() {}
    ...
};

Nie zajdzie daleko bez potencjalnie epickiego przepisywania, ponieważ pomysł abstrakcji na poziomie pojedynczego piksela jest już niezwykle nieefektywny ( vptrsam kosztowałby często więcej pamięci niż cały piksel) w porównaniu do abstrakcji na poziomie obrazu (który będzie często reprezentują miliony pikseli). Dlatego z góry zastanów się nad przedstawieniami danych, abyś nie musiał stawić czoła takiemu koszmarnemu scenariuszowi, a najlepiej nie więcej, ale myślę, że warto o tym pomyśleć z góry, ponieważ nie chcesz budować skomplikowany silnik wokół ECS i przekonaj się, że sam ECS stanowi wąskie gardło w zakresie, który wymaga zmiany rzeczy na poziomie projektu.

Jeśli chodzi o pomyłki w pamięci podręcznej ECS, moim zdaniem programiści często starają się, aby ich ECS był przyjazny dla pamięci podręcznej. Zaczyna dawać zbyt mały huk, aby złotówka próbowała uzyskać dostęp do wszystkich komponentów w idealnie ciągły sposób, i często oznacza kopiowanie i tasowanie danych w dowolnym miejscu. Zwykle wystarczy, powiedzmy, po prostu posortować indeksy składników sortowania przed uzyskaniem dostępu do nich, aby uzyskać do nich dostęp w sposób, w którym przynajmniej nie ładujesz obszaru pamięci do linii pamięci podręcznej, tylko aby go eksmitować, a następnie załadować wszystko od nowa w tej samej pętli, aby uzyskać dostęp do innej części tej samej linii pamięci podręcznej. I ECS nie musi zapewniać niesamowitej wydajności na całym pokładzie. To nie jest tak, że system wejściowy czerpie z tego tyle korzyści, co fizyka lub system renderowania, więc zalecam dążenie do „dobrego” ogólna wydajność i „doskonała” w miejscach, w których naprawdę jej potrzebujesz. To powiedziawszy, użyjunordered_mapi stringtutaj są wystarczająco łatwe do uniknięcia.


źródło