Kiedy piszę kod, zawsze staram się, aby mój kod był jak najbardziej czysty i czytelny.
Od czasu do czasu przychodzi czas, kiedy trzeba przekroczyć linię i przejść od ładnego czystego kodu do nieco brzydszego kodu, aby był szybszy.
Kiedy można przekroczyć tę linię?
performance
optimization
quality
readability
maintainability
Ken Cochrane
źródło
źródło
Odpowiedzi:
Przekraczasz linię, kiedy
Oto prawdziwy przykład: uruchomiony przeze mnie eksperymentalny system zbyt wolno generował dane, zajmując ponad 9 godzin pracy i zużywając tylko 40% procesora. Zamiast zbytnio popsuć kod, przeniosłem wszystkie pliki tymczasowe do systemu plików w pamięci. Dodano 8 nowych wierszy nieprzyjemnego kodu, a teraz wykorzystanie procesora przekracza 98%. Problem rozwiązany; nie wymaga brzydoty.
źródło
foo
i zmieniam jej nazwęfoo_ref
- zwykle znajduje się ona bezpośrednio nadfoo
plikiem źródłowym. W mojej uprzęży testowej wzywamfoo
orazfoo_ref
do walidacji i pomiaru wydajności względnej.To fałszywa dychotomia. Możesz sprawić, że kod będzie szybki i łatwy w utrzymaniu.
Sposób, w jaki to robisz, polega na pisaniu go czysto, szczególnie przy możliwie najprostszej strukturze danych.
Następnie dowiadujesz się, gdzie są upływy czasu (uruchamiając go, po napisaniu go, a nie wcześniej) i naprawiasz je jeden po drugim. (Oto przykład.)
Dodano: Zawsze słyszymy o kompromisach, prawda, takich jak kompromis między czasem a pamięcią, czy kompromis między szybkością a utrzymywalnością? Chociaż takie krzywe mogą istnieć, nie należy zakładać, że jakikolwiek program jest na krzywej , a nawet gdziekolwiek w jej pobliżu.
Każdy program, który znajduje się na krzywej, można łatwo (poprzez przekazanie go do pewnego rodzaju programatora) uczynić zarówno znacznie wolniejszym, jak i znacznie mniejszym w utrzymaniu, a wtedy nie będzie w pobliżu krzywej. Taki program ma wtedy dużo miejsca, aby był szybszy i łatwiejszy w utrzymaniu.
Z mojego doświadczenia wynika, że zaczyna się wiele programów.
źródło
W moim istnieniu OSS wykonuję wiele prac bibliotecznych mających na celu zwiększenie wydajności, które są ściśle związane ze strukturą danych osoby wywołującej (tj. Zewnętrzną względem biblioteki), bez (z założenia) mandatu dla typów przychodzących. Tutaj najlepszym sposobem na uczynienie tego performera jest metaprogramowanie, co (ponieważ jestem w .NET-land) oznacza emisje IL. To jakiś brzydki, brzydki kod, ale bardzo szybki.
W ten sposób z radością akceptuję kod biblioteki może być „brzydszy” niż kod aplikacji , po prostu dlatego, że ma mniejszą (lub może nie) kontrolę nad danymi wejściowymi , więc musi realizować niektóre zadania za pomocą różnych mechanizmów. Lub jak wyraziłem to innego dnia:
Teraz kod aplikacji jest nieco inny, ponieważ w tym miejscu „zwykli” (rozsądni) programiści zwykle inwestują dużą część swojego czasu pracy / współpracy; cele i oczekiwania każdego z nich są (IMO) nieco inne.
IMO, powyższe odpowiedzi sugerujące, że może być szybki i łatwy w utrzymaniu, odnoszą się do kodu aplikacji, w którym programista ma większą kontrolę nad strukturami danych i nie używa narzędzi takich jak metaprogramowanie. To powiedziawszy, istnieją różne sposoby wykonywania metaprogramowania, różne poziomy szaleństwa i różne poziomy kosztów ogólnych. Nawet na tej arenie musisz wybrać odpowiedni poziom abstrakcji. Ale kiedy aktywnie, pozytywnie, naprawdę chcesz, aby obsługiwał nieoczekiwane dane w absolutnie najszybszy sposób; to może stać się brzydkie. Sobie z tym poradzić; p
źródło
Po sprofilowaniu kodu i upewnieniu się, że w rzeczywistości powoduje on znaczne spowolnienie.
źródło
Czysty kod niekoniecznie jest wyłączny w przypadku szybko wykonywanego kodu. Normalnie trudny do odczytania kod został napisany, ponieważ pisanie było szybsze, a nie dlatego, że działa szybciej.
Pisanie „brudnego” kodu w celu przyspieszenia go jest prawdopodobnie nierozsądne, ponieważ nie wiadomo na pewno, że wprowadzone zmiany rzeczywiście coś poprawiają. Knuth ujął to najlepiej:
Innymi słowy, najpierw napisz kod czysty. Następnie profiluj wynikowy program i sprawdź, czy ten segment jest w rzeczywistości wąskim gardłem wydajności. Jeśli tak, zoptymalizuj sekcję, jeśli to konieczne, i pamiętaj o dołączeniu dużej ilości komentarzy do dokumentacji (być może łącznie z oryginalnym kodem) w celu wyjaśnienia optymalizacji. Następnie profiluj wynik, aby sprawdzić, czy rzeczywiście dokonałeś poprawy.
źródło
Ponieważ pytanie brzmi „szybki, trudny do odczytania kod”, prosta odpowiedź nigdy nie jest taka. Nigdy nie ma usprawiedliwienia dla pisania trudnego do odczytania kodu. Dlaczego? Dwa powody.
źródło
Gdy jest to kod wyrzucany. To znaczy, że dosłownie: kiedy napisać skrypt, aby wykonać obliczenie jednorazowej lub zadanie, a wiem z taką pewnością, że nigdy nie będzie musiał zrobić to działanie ponownie, że można „rm source-file” bez wahania, po czym możesz wybierać brzydka trasa.
W przeciwnym razie jest to fałszywa dychotomia - jeśli uważasz, że musisz uczynić to brzydszym, aby zrobić to szybciej, robisz to źle. (Lub twoje zasady dotyczące tego, co jest dobrym kodem, wymagają korekty. Korzystanie z goto jest w rzeczywistości dość eleganckie, gdy jest właściwym rozwiązaniem problemu. Jednak rzadko tak jest.)
źródło
Ilekroć szacunkowy koszt niższej wydajności na rynku jest większy niż szacowany koszt utrzymania kodu dla danego modułu kodu.
Ludzie wciąż robią skręcone ręcznie kodowane SSE / NEON / itp. montaż, aby spróbować pokonać oprogramowanie konkurencji na popularnym tegorocznym chipie CPU.
źródło
Nie zapominaj, że możesz uczynić trudny do odczytania kod łatwym do zrozumienia dzięki odpowiedniej dokumentacji i komentarzom.
Ogólnie rzecz biorąc, profil po napisaniu łatwego do odczytania kodu, który wykonuje żądaną funkcję. Wąskie gardła mogą wymagać od ciebie zrobienia czegoś, co sprawia, że wygląda to bardziej skomplikowane, ale naprawiasz to poprzez wyjaśnienie siebie.
źródło
Dla mnie jest to proporcja stabilności (jak w przypadku cementu w betonie, wypalanej w piecu gliny, osadzonej w kamieniu, zapisanej trwałym atramentem). Im bardziej niestabilny jest twój kod, ponieważ im większe prawdopodobieństwo, że będziesz musiał go zmienić w przyszłości, tym łatwiej będzie go elastycznie, podobnie jak mokra glina, aby pozostać produktywnym. Podkreślam także elastyczność i brak czytelności. Dla mnie łatwość zmiany kodu jest ważniejsza niż łatwość czytania. Kod może być łatwy do odczytania, a koszmar do zmiany, a jaki pożytek jest w stanie odczytać i łatwo zrozumieć szczegóły implementacji, jeśli koszmar do zmiany? O ile nie jest to tylko ćwiczenie akademickie, zwykle celem jest łatwe zrozumienie kodu w bazie kodu produkcyjnego z zamiarem łatwiejszej zmiany go w razie potrzeby. Jeśli trudno to zmienić, wtedy wiele zalet czytelności wychodzi poza okno. Czytelność jest ogólnie użyteczna tylko w kontekście elastyczności, a elastyczność jest użyteczna tylko w kontekście niestabilności.
Oczywiście nawet najtrudniejszy do utrzymania kod, jaki można sobie wyobrazić, bez względu na to, jak łatwy lub trudny jest jego odczyt, nie stanowi problemu, jeśli nigdy nie ma powodu, aby go zmieniać, tylko go używać. Możliwe jest osiągnięcie takiej jakości, szczególnie w przypadku niskiego poziomu kodu systemowego, w którym wydajność często liczy się najbardziej. Mam kod C, którego wciąż używam, co nie zmieniło się od późnych lat 80-tych. Od tego czasu nie musiał się zmieniać. Kod jest niezgrabny, napisany w czasach grzebania w bitach i ledwo go rozumiem. Nadal jednak ma zastosowanie do dzisiaj i nie muszę rozumieć jego implementacji, aby czerpać z niego wiele korzyści.
Dokładne pisanie testów jest jednym ze sposobów poprawy stabilności. Innym jest oddzielenie płatności od produkcji. Jeśli twój kod nie zależy od niczego innego, to jedynym powodem do zmiany jest to, że sam musi się zmienić. Czasami niewielka ilość duplikacji kodu może służyć jako mechanizm odsprzęgający, który radykalnie poprawia stabilność w sposób, który czyni go godnym kompromisem, jeśli w zamian otrzymujesz kod, który jest teraz całkowicie niezależny od wszystkiego innego. Teraz ten kod jest niewrażliwy na zmiany w świecie zewnętrznym. Tymczasem kod, który zależy od 10 różnych bibliotek zewnętrznych, ma 10-krotny powód do zmiany w przyszłości.
Inną przydatną rzeczą w praktyce jest oddzielenie biblioteki od niestabilnych części bazy kodu, być może nawet zbudowanie jej osobno, tak jak w przypadku bibliotek stron trzecich (które podobnie mają być używane, a nie zmieniane, a przynajmniej nie przez zespół). Tylko ten rodzaj organizacji może zapobiec manipulowaniu nią przez ludzi.
Kolejny to minimalizm. Im mniej Twój kod próbuje zrobić, tym większe prawdopodobieństwo, że potrafi zrobić to, co robi dobrze. Monolityczne konstrukcje są prawie trwale niestabilne, ponieważ im więcej funkcji jest dodawanych, tym bardziej wydają się niekompletne.
Stabilność powinna być Twoim głównym celem za każdym razem, gdy chcesz napisać kod, który nieuchronnie będzie trudny do zmiany, jak na przykład równoległy kod SIMD, który został dostrojony do mikroukładu. Przeciwdziałasz trudnościom w utrzymywaniu kodu, maksymalizując prawdopodobieństwo, że nie będziesz musiał go zmieniać, a zatem nie będziesz musiał go utrzymywać w przyszłości. To obniża koszty utrzymania do zera, bez względu na to, jak trudny jest kod.
źródło