Być może uda Ci się przekonać współpracownika z przyczyn technicznych, że kompilator może je wbudować, więc ta „optymalizacja” na poziomie źródła nie jest pomocna.
Mam nadzieję, że przesadzasz z jego wypowiedziami o tym, że jest to cała metoda 3000 linii najbardziej zoptymalizowana z możliwych , inaczej twój współpracownik prawdopodobnie nie ma pojęcia o optymalizacji wydajności i po prostu chwyci się czegoś, co przeczytał raz. Ludzi, którzy myślą, że coś wiedzą, ale naprawdę nie rozumieją, czasami najtrudniej jest przekonać. Kilka razy wymieniłem się komentarzami dotyczącymi przepełnienia stosu, z ludźmi, którzy nie chcieli uwierzyć, że się mylili, ale nie byli w stanie podać spójnego technicznego wyjaśnienia, które miałoby jakikolwiek sens.
Jako ekspert ds. Optymalizacji asm [x86], [assembler], [performance], [sse] tagi itp.), mogę powiedzieć, że jest prawie niemożliwe, aby ta funkcja była „najbardziej zoptymalizowana z możliwych”, nawet jeśli Twój współpracownik spędził lata na profilowaniu i dostosowywaniu ( na jakimś konkretnym sprzęcie? z określoną wersją systemu operacyjnego i kompilatora?). Tak duża funkcja zawsze będzie miała miejsce na drobne poprawki (lub nowe pomysły na duże zmiany), które mogą ją przyspieszyć lub zmniejszyć (kod maszynowy) z tą samą prędkością (być może bardziej przyjazna dla hiperwątkowości, aby wykonać tę samą pracę w mniejszej liczbie instrukcji) .
Nie sądzę, żeby kompilator C # + JIT był tak zły, że nie może wywołać metody wbudowanej, zwłaszcza jeśli mają tylko jedną witrynę wywołań . Nie znam języka C # (głównie C i C ++), ale czy ma coś takiego jak statyczna wbudowana
funkcja niebędąca składnikiem, którą kompilator może wbudować zamiast emitować stojak -samodzielna definicja dla i zrobi to, nawet jeśli funkcja jest duża? Lub coś takiego jak GNU C __attribute __ ((always_inline))
? Twój współpracownik może to wykorzystać, aby poczuć, że uzyskuje optymalizację, którą uważa za ważną, bez powodowania nieprzyjemnego bałaganu w źródle.
Ale co ważniejsze, „optymalizacja” jest warta kompromisu w zakresie czytelności tylko wtedy, gdy prosta wersja bazowa (którą napisałeś jako punkt wyjścia i do porównania z wersją zoptymalizowaną) jest wolniejsza niż chcesz silny>. Nie możesz stwierdzić, czy faktycznie optymalizujesz cokolwiek, jeśli nie masz punktu wyjścia, z którym mógłbyś porównać, a tym samym ocenić jakąkolwiek kompromis w zakresie czytelności lub rozmiaru kodu maszynowego / pamięci podręcznej instrukcji w porównaniu z przyspieszeniem.
Pisanie mniej czytelnej „zoptymalizowanej” wersji bez prostej linii bazowej jest zwykle błędem , chyba że już myślisz, że wiesz z doświadczenia, jak skompilowałaby się prosta wersja i że nie byłaby wydajna dość. Zwykle masz prostą wersję jako część testu jednostkowego dla ręcznie rozwiniętej / zwektoryzowanej wersji. (Ten przypadek wstawiania ręcznego może być jednak inny. Nie sprawia, że żaden pojedynczy element logiki nie jest bardziej złożony ani „dziwnie” zaimplementowany. A może tak jest? Czy istnieje ręczna optymalizacja między blokami?)
bardzo często jest tego wart w przypadku małych funkcji, ale wywołanie dużego bloku kodu z wielu witryn wywołań tylko raz wykorzystuje ślad pamięci podręcznej instrukcji. Jednak za każdym razem płaci narzut wywołania funkcji, więc mikroznakowanie tylko jednej funkcji, bez pełnego kontekstu programu, może sprawić, że nadmierne wstawianie i rozwijanie będzie wyglądać dobrze. Zwykle nie ma problemu, pozostawiając decyzję nowoczesnej heurystyce kompilatora; są zwykle dość dobrze dostrojone, zwłaszcza jeśli potrafią przeprowadzić optymalizację sterowaną profilem, aby znaleźć pętle, które są naprawdę gorące. (Kompilatory JIT działają w czasie wykonywania, więc mają dane do profilowania, jeśli chcą ich używać. Zwykle od stworzenia nie w pełni zoptymalizowanej wersji lub najpierw zinterpretowania, a następnie użycia danych profilowania do spekulatywnego wbudowania metod wirtualnych i tym podobnych.) / p>
Czasami optymalizacja nie szkodzi czytelności, ale w tym przypadku wyraźnie tak.
W C ++ często piszę małe statyczne inline
funkcje pomocnicze, które będą wbudowane w większą funkcję Optymalizuję bzdury za pomocą wewnętrznych elementów SIMD. Kiedy patrzę na asm wygenerowany przez kompilator, widać dokładnie zero wady w wydajności kodu maszynowego i niezły plus w czytelności źródła. Żadna samodzielna definicja nie pojawia się nigdzie w pliku wykonywalnym dla tych funkcji pomocniczych, więc nawet nie nadużywają pliku wykonywalnego.
Jeśli chcesz wcisnąć problem, zapytaj współpracownika, czy przyjrzeli się wynikom asm kompilatora JIT pod kątem ich metody i sprofilowali je, i stwierdzili, że te duże bloki warunkowe pozwoliły kompilatorowi na optymalizację w sposób, w jaki nie byłby w stanie tego zrobić przy wstawianiu.
Świadomość tego, co + + kompilator sprzęt może działać wydajnie nie zawsze jest złą rzeczą, jeśli pozwolisz, aby informowało to o wyborach kodowania, gdy nie szkodzi to czytelności.
Kuszące jest wciągnięcie w optymalizację czegoś, co nie wymaga optymalizacji . Zwłaszcza jeśli myślisz tylko o optymalizacji szybkości tej jednej funkcji, jeśli została wywołana w gorącej pętli , gdy nie będzie ona w gorącej pętli. Jeśli jest wywoływany rzadko, pamięć podręczna kodu może być zimna, więc kompaktowy jest lepszy. (Mniej wierszy kodu w pamięci podręcznej do załadowania z pamięci głównej.)
Ten rodzaj argumentów pomaga tylko wtedy, gdy można rozliczyć typowe funkcje pomocnicze z tej 3000-liniowej metody. Umieszczenie każdego bloku w osobnej funkcji nie zmniejszy kodu maszynowego. Może to wpłynąć na logikę decyzyjną, dla której funkcji należy wysłać do bardziej zlokalizowanej, co skutkuje mniejszym zużyciem pamięci podręcznej I-cache. I może mniej stron 4k dotkniętych / załadowanych z dysku / i-TLBfootprint.