Jak uniknąć klątwy przedwczesnej optymalizacji?

Opublikowany: 2022-03-11

To prawie godne gwarancji, naprawdę. Od nowicjuszy po ekspertów, od architektury po ASM i optymalizację wszystkiego, od wydajności maszyny po wydajność programistów, są całkiem spore szanse, że ty i twój zespół nie osiągacie własnych celów.

Co? Ja? Mój zespół?

To dość mocne oskarżenie, żeby wyrównać. Pozwól mi wyjaśnić.

Optymalizacja nie jest świętym Graalem, ale może być równie trudna do uzyskania. Chcę podzielić się z wami kilkoma prostymi wskazówkami (i górą pułapek), które pomogą zmienić doświadczenie zespołu z samo-sabotażu w harmonię, spełnienie, równowagę i ostatecznie optymalizację.

Co to jest przedwczesna optymalizacja?

Przedwczesna optymalizacja próbuje zoptymalizować wydajność:

  1. Przy pierwszym kodowaniu algorytmu
  2. Zanim testy porównawcze potwierdzą, że musisz
  3. Przed profilowaniem wskazuje, gdzie warto zajmować się optymalizacją
  4. Na niższym poziomie niż obecnie dyktuje Twój projekt

Teraz jestem optymistą, Optimus.

Przynajmniej będę udawać optymistę podczas pisania tego artykułu. Ze swojej strony możesz udawać, że nazywasz się Optimus, więc to przemówi do Ciebie bardziej bezpośrednio.

Jako osoba zajmująca się technologią, prawdopodobnie czasami zastanawiasz się, jak to może być $year , a jednak pomimo całego naszego postępu, jest to w jakiś sposób akceptowalny standard dla $task , które jest tak irytująco czasochłonne. Chcesz być szczupły. Wydajny. Świetny. Kogoś takiego jak programiści Rockstar, o których domagają się te oferty pracy, ale z ciosami lidera. Więc kiedy twój zespół pisze kod, zachęcasz go, aby zrobili to dobrze za pierwszym razem (nawet jeśli „właściwe” jest tutaj bardzo względnym terminem). Wiedzą, że to jest droga Sprytnego Kodera, a także droga tych, którzy nie muszą tracić czasu na późniejszą refaktoryzację.

Czuję to. Siła perfekcjonizmu też jest we mnie czasami silna. Chcesz, aby Twój zespół poświęcił teraz trochę czasu, aby zaoszczędzić dużo czasu później, ponieważ wszyscy przedzierają się przez swoją część „Gówniany kod, który inni ludzie napisali (co do diabła myśleli?)”. To w skrócie SCOPWWHWTT, ponieważ wiem, że lubisz niewymawialne akronimy.

Wiem też, że nie chcesz, aby kod twojego zespołu był taki dla nich, jak i dla kogokolwiek innego.

Zobaczmy więc, co można zrobić, aby poprowadzić swój zespół we właściwym kierunku.

Co zoptymalizować: witamy w tej sztuce

Przede wszystkim, kiedy myślimy o optymalizacji programu, często od razu zakładamy, że mówimy o wydajności. Nawet to jest już bardziej niejasne, niż mogłoby się wydawać (szybkość? wykorzystanie pamięci? itp.), więc zatrzymajmy się w tym miejscu.

Uczyńmy to jeszcze bardziej niejednoznacznymi! Tylko na początku.

Mój pajęczyny mózg lubi wprowadzać porządek tam, gdzie to możliwe, więc z optymizmem uznam, że to, co mam do powiedzenia, jest dobre .

Istnieje prosta zasada optymalizacji (wydajności), która brzmi : Nie rób tego . Brzmi to dość prosto, ale nie wszyscy się z tym zgadzają. Ja też się z tym nie zgadzam. Niektórzy ludzie po prostu napiszą lepszy kod z bramy niż inni. Miejmy nadzieję, że dla każdej osoby jakość kodu, który napisaliby w zupełnie nowym projekcie, z czasem ogólnie się poprawi. Ale wiem, że dla wielu programistów tak się nie stanie, bo im więcej będą wiedzieć, tym więcej sposobów na przedwczesną optymalizację będą się kusić.

Dla wielu programistów… im więcej wiedzą, tym bardziej będą kusić ich przedwczesna optymalizacja.

Więc to Nie rób tego nie może być nauką ścisłą, ale ma tylko przeciwdziałać wewnętrznej potrzebie typowego technika do rozwiązania zagadki. To przecież przede wszystkim przyciąga wielu programistów do rzemiosła. Rozumiem. Ale poproś ich, aby to ocalili , aby oprzeć się pokusie. Jeśli ktoś potrzebuje teraz rozwiązania łamigłówki, zawsze może pobawić się w Sudoku z niedzielnej gazety, wziąć książkę Mensa lub zagrać w golfa z jakimś sztucznym problemem. Ale pozostaw to poza repozytorium do właściwego czasu. Prawie zawsze jest to mądrzejsza droga niż wstępna optymalizacja.

Pamiętaj, ta praktyka jest na tyle znana, że ​​ludzie pytają, czy przedwczesna optymalizacja jest źródłem wszelkiego zła. (Nie posunąłbym się aż tak daleko, ale zgadzam się z sentymentem.)

Nie mówię, że powinniśmy wybrać najbardziej zabójczy dla mózgu sposób, jaki możemy wymyślić na każdym poziomie projektowania. Oczywiście nie. Ale zamiast wybierać najmądrzejszy wygląd, możemy wziąć pod uwagę inne wartości:

  1. Najłatwiej wyjaśnić nowemu pracownikowi
  2. Największe prawdopodobieństwo zdania recenzji kodu przez Twojego najbardziej doświadczonego programistę
  3. Najbardziej łatwy w utrzymaniu
  4. Najszybciej napisać
  5. Najłatwiejszy do przetestowania
  6. Najbardziej przenośny
  7. itp.

Ale tutaj problem okazuje się trudny. Nie chodzi tylko o unikanie optymalizacji pod kątem szybkości, rozmiaru kodu, wykorzystania pamięci, elastyczności czy przyszłości. Chodzi o równowagę i o to, czy to, co robisz, jest rzeczywiście zgodne z Twoimi wartościami i celami. Jest to całkowicie kontekstowe, a czasem nawet niemożliwe do obiektywnego zmierzenia.

To sztuka. (Por. Sztuka programowania komputerowego.)

Dlaczego to dobrze? Bo takie jest życie. To jest niechlujne. Nasze zorientowane na programowanie mózgi czasami tak bardzo chcą zaprowadzić porządek w chaosie, że w końcu ironicznie pomnożymy chaos. To jak paradoks próby zmuszenia kogoś, by cię kochał. Jeśli myślisz, że ci się to udało, to już nie jest miłość; tymczasem jesteś oskarżony o branie zakładników, prawdopodobnie potrzebujesz więcej miłości niż kiedykolwiek, a ta metafora musi być jedną z najbardziej niezręcznych, jakie mogłem wybrać.

W każdym razie, jeśli uważasz, że znalazłeś idealny system do czegoś, cóż… chyba ciesz się iluzją, dopóki trwa. W porządku, porażki to wspaniała okazja do nauki.

Przedwczesna optymalizacja często niepotrzebnie i bezowocnie komplikuje Twój produkt.

Pamiętaj o UX

Przyjrzyjmy się, jak doświadczenie użytkownika wpisuje się w te potencjalne priorytety. W końcu nawet chcieć, aby coś dobrze działało, dotyczy na pewnym poziomie UX.

Jeśli pracujesz nad interfejsem użytkownika, bez względu na to, jakiego frameworka lub języka używa kod, będzie pewna ilość schematu i powtórzeń. Zdecydowanie może być cenne pod względem czasu programisty i przejrzystości kodu, aby spróbować to zredukować. Aby pomóc w sztuce równoważenia priorytetów, chcę podzielić się kilkoma historiami.

W jednym miejscu pracy firma, w której pracowałem, korzystała z systemu korporacyjnego o zamkniętym kodzie źródłowym, opartego na upartym stosie technologicznym. W rzeczywistości był tak uparty, że sprzedawca, który nam go sprzedał, odmówił dostosowania interfejsu użytkownika, który nie pasował do opinii stosu, ponieważ było to tak bolesne dla ich programistów. Nigdy nie używałem ich stosu, więc nie potępiam ich za to, ale faktem było, że ten kompromis „dobry dla programisty, zły dla użytkownika” był tak uciążliwy dla moich współpracowników w pewnych kontekstach, że skończyłem napisanie zewnętrznego dodatku ponownie wdrażającego tę część interfejsu użytkownika systemu. (Był to ogromny wzrost wydajności. Moi współpracownicy to pokochali! Ponad dekadę później nadal oszczędza wszystkim czas i frustrację).

Nie mówię, że opinia jest problemem samym w sobie; tylko, że zbyt wiele z tego stało się problemem w naszym przypadku. Jako kontrprzykład, jedną z największych zalet Ruby on Rails jest właśnie to, że jest uparty, w ekosystemie front-endu, w którym łatwo doznaje się zawrotów głowy z powodu zbyt wielu opcji. (Daj mi coś z opiniami, dopóki nie zrozumiem własnej!)

W przeciwieństwie do tego, możesz pokusić się o koronację UX na króla wszystkiego w swoim projekcie. Cel godny, ale pozwólcie, że opowiem moją drugą historię.

Kilka lat po sukcesie powyższego projektu, jeden z moich współpracowników przyszedł do mnie z prośbą o zoptymalizowanie UX poprzez zautomatyzowanie pewnego bałaganu z rzeczywistego scenariusza, który czasami się pojawiał, tak aby można go było rozwiązać jednym kliknięciem. Zacząłem analizować, czy jest w ogóle możliwe zaprojektowanie algorytmu, który nie będzie miał żadnych fałszywych alarmów lub negatywów z powodu wielu dziwnych przypadków brzegowych scenariusza. Im dłużej rozmawiałem o tym z moim współpracownikiem, tym bardziej zdawałem sobie sprawę, że wymagania po prostu się nie opłacają. Scenariusz pojawiał się tylko raz na jakiś czas — powiedzmy co miesiąc — i obecnie rozwiązanie go zajmuje jednej osobie kilka minut. Nawet gdybyśmy mogli z powodzeniem ją zautomatyzować, bez żadnych błędów, zajęłoby wieki, zanim wymagany czas rozwoju i konserwacji zwróciłby się w postaci czasu zaoszczędzonego przez moich współpracowników. Miłośnik ludzi we mnie miał trudny moment, mówiąc „nie”, ale musiałem skrócić rozmowę.

Niech więc komputer zrobi, co w jego mocy, aby pomóc użytkownikowi, ale tylko w rozsądnym zakresie. Skąd jednak wiesz, jaki to stopień?

Co jest łatwe i trudne dla komputerów

Podejściem, które lubię, jest profilowanie UX tak, jak twoi programiści profilują swój kod. Dowiedz się od użytkowników, gdzie spędzają najwięcej czasu, klikając lub wpisując w kółko to samo, i sprawdź, czy możesz zoptymalizować te interakcje. Czy twój kod może zgadywać, co najprawdopodobniej wprowadzi, i ustawić to jako domyślny brak danych wejściowych? Poza pewnymi zabronionymi kontekstami (potwierdzenie umowy EULA bez kliknięcia?) może to naprawdę wpłynąć na produktywność i zadowolenie użytkowników. Jeśli możesz, przeprowadź testy użyteczności na korytarzu. Czasami możesz mieć problem z wyjaśnieniem, w czym komputery mogą łatwo pomóc, a w czym nie… ale ogólnie rzecz biorąc, ta wartość może mieć bardzo duże znaczenie dla użytkowników.

Unikanie przedwczesnej optymalizacji: kiedy i jak optymalizować

Pomimo naszej eksploracji innych kontekstów, załóżmy teraz wyraźnie, że optymalizujemy pewien aspekt surowej wydajności maszyny do końca tego artykułu. Moje sugerowane podejście odnosi się również do innych celów, takich jak elastyczność, ale każdy cel będzie miał swoje własne pułapki; najważniejsze jest to, że przedwczesna optymalizacja pod kątem czegokolwiek prawdopodobnie się nie powiedzie.

A zatem, pod względem wydajności, jakie metody optymalizacji należy zastosować? Zagłębmy się.

To nie jest inicjatywa oddolna, to potrójne Eh

TL; DR to: Pracuj od góry. Optymalizacje wyższego poziomu można wykonać wcześniej w projekcie, a optymalizacje niższego poziomu należy odłożyć na później. To wszystko, czego potrzebujesz, aby zrozumieć znaczenie wyrażenia „przedwczesna optymalizacja”; robienie rzeczy niezgodnie z tą kolejnością wiąże się z dużym prawdopodobieństwem marnowania czasu zespołu i przeciwdziałania efektom. W końcu nie piszesz całego projektu w kodzie maszynowym od samego początku, prawda? Tak więc nasz modus operandi AAA polega na optymalizacji w następującej kolejności:

  1. Architektura
  2. Algorytmy
  3. montaż

Powszechnie uważa się, że algorytmy i struktury danych są często najskuteczniejszymi miejscami do optymalizacji, przynajmniej jeśli chodzi o wydajność. Należy jednak pamiętać, że architektura czasami określa, które algorytmy i struktury danych mogą być w ogóle użyte.

Kiedyś odkryłem oprogramowanie do sporządzania raportów finansowych, wielokrotnie przeszukując bazę danych SQL dla każdej transakcji finansowej, a następnie wykonując bardzo podstawowe obliczenia po stronie klienta. Małe firmy korzystające z oprogramowania zajęły zaledwie kilka miesięcy użytkowania, zanim nawet stosunkowo niewielka ilość danych finansowych sprawiła, że ​​przy zupełnie nowych komputerach stacjonarnych i dość mocnym serwerze czas generowania raportu wynosił już do kilku minut, a to było jeden, którego musieli używać dość często. Skończyło się na napisaniu prostego polecenia SQL, które zawierało logikę sumowania — udaremniając ich architekturę, przenosząc pracę na serwer, aby uniknąć wszystkich duplikatów i okrążeń sieci — a nawet dane z kilku lat później, moja wersja mogła generować ten sam raport w ciągu zaledwie milisekund na tym samym starym sprzęcie.

Czasami nie masz wpływu na architekturę projektu, ponieważ jest już za późno, aby zmiana architektury była możliwa. Czasami twoi programiści mogą to ominąć, tak jak to zrobiłem w powyższym przykładzie. Ale jeśli jesteś na początku projektu i masz coś do powiedzenia na temat jego architektury, teraz nadszedł czas, aby to zoptymalizować.

Architektura

„Gdyby budowniczowie budowali budynki w sposób, w jaki programiści pisali programy, pierwszy dzięcioł, który się pojawił, zniszczyłby cywilizację”. --Prawo Weinberga

W projekcie architektura jest najdroższą częścią do zmiany po fakcie, więc jest to miejsce, w którym optymalizacja może mieć sens na początku. Jeśli Twoja aplikacja ma na przykład dostarczać dane za pośrednictwem strusi, warto uporządkować ją w kierunku pakietów o niskiej częstotliwości i dużej ładowności, aby uniknąć pogorszenia wąskiego gardła. W takim przypadku lepiej mieć pełną implementację Tetrisa, aby zapewnić rozrywkę użytkownikom, ponieważ spinner ładowania po prostu go nie załatwi. (Żarty na bok: lata temu instalowałem swoją pierwszą dystrybucję Linuksa, Corel Linux 2.0, i byłem zachwycony, że długotrwały proces instalacji zawierał właśnie to. Widząc tyle razy ekrany informacyjne instalatora Windows 95, że zapamiętałem je, to był wtedy powiewem świeżego powietrza.)

Jako przykład drogiej zmiany architektonicznej, powód, dla którego wspomniany raport SQL jest tak wysoce nieskalowalny, wynika z jego historii. Aplikacja ewoluowała z biegiem czasu, od swoich korzeni w MS-DOS i domowej, niestandardowej bazie danych, która nie była nawet pierwotnie przeznaczona dla wielu użytkowników. Kiedy dostawca w końcu przeszedł na SQL, wydaje się, że schemat i kod raportowania zostały przeniesione jeden za jeden. To pozwoliło im na imponującą poprawę wydajności o ponad 1000% od lat, którą można było rozsypać podczas aktualizacji, za każdym razem, gdy zbliżali się do ukończenia zmiany architektury, faktycznie wykorzystując zalety SQL dla danego raportu. Dobry dla biznesu z zamkniętymi klientami, takimi jak mój ówczesny pracodawca, i wyraźnie próbujący nadać priorytet wydajności kodowania podczas początkowej zmiany. Ale w niektórych przypadkach zaspokajanie potrzeb klientów jest tak skuteczne, jak młotek obraca śrubę.

Architektura polega po części na przewidywaniu, w jakim stopniu Twój projekt będzie musiał być skalowany iw jaki sposób. Ponieważ architektura jest na tak wysokim poziomie, trudno jest skonkretyzować nasze „zalecenia i zakazy” bez zawężania naszej uwagi do konkretnych technologii i dziedzin.

Nie nazwałbym tego tak, ale wszyscy inni tak

Na szczęście Internet jest pełen zebranej wiedzy na temat niemal każdego rodzaju architektury, jaki kiedykolwiek wymyślono. Kiedy wiesz, że nadszedł czas, aby zoptymalizować swoją architekturę, badanie pułapek sprowadza się w zasadzie do znalezienia modnego słowa opisującego twoją genialną wizję. Są szanse, że ktoś myślał podobnie jak ty, próbował tego, zawiódł, powtórzył i opublikował o tym na blogu lub w książce.

Identyfikacja buzzword może być trudna do osiągnięcia przez samo wyszukiwanie, ponieważ dla tego, co nazywasz FLDSMDFR, ktoś inny już ukuł termin SCOPWWHWTT i opisuje ten sam problem, który rozwiązujesz, ale używając zupełnie innego słownictwa niż ty. Społeczności programistów na ratunek! Uderz w StackExchange lub HashNode z tak dokładnym opisem, jak to tylko możliwe, oraz wszystkimi modnymi słowami, którymi nie jest Twoja architektura , aby wiedzieli, że wykonałeś wystarczające wstępne badania. Ktoś z przyjemnością cię oświeci.

Tymczasem kilka ogólnych porad może być dobrym materiałem do przemyśleń.

Algorytmy i montaż

Biorąc pod uwagę sprzyjającą architekturę, tutaj programiści w twoim zespole otrzymają najwięcej T-blingów w swoim czasie. Podstawowe unikanie przedwczesnej optymalizacji również ma tu zastosowanie, ale twoi programiści dobrze zrobią, jeśli rozważą niektóre szczegóły na tym poziomie. Jest tak wiele do przemyślenia, jeśli chodzi o szczegóły implementacji, że napisałem osobny artykuł o optymalizacji kodu z myślą o programistach pierwszej linii i starszych programistów.

Ale kiedy ty i twój zespół wdrożycie coś, co nie jest zoptymalizowane pod względem wydajności, czy naprawdę zostawiacie to na Nie rób tego ? Nigdy nie optymalizujesz?

Masz rację. Następną zasadą jest, tylko dla ekspertów, nie rób tego jeszcze .

Czas na benchmark!

Twój kod działa. Może jest tak powolny, że już wiesz , że będziesz musiał zoptymalizować, ponieważ jest to kod, który będzie często uruchamiany. Może nie jesteś pewien lub masz algorytm O(n) i uważasz, że prawdopodobnie wszystko jest w porządku. Niezależnie od przypadku, jeśli ten algorytm może być kiedykolwiek wart optymalizacji, moja rekomendacja w tym momencie jest taka sama: uruchom prosty test porównawczy.

Czemu? Czy nie jest jasne, że mój algorytm O(n³) nie może być gorszy niż cokolwiek innego? Cóż, z dwóch powodów:

  1. Możesz dodać test porównawczy do swojego zestawu testów jako obiektywną miarę swoich celów wydajnościowych, niezależnie od tego, czy są one obecnie osiągane.
  2. Nawet eksperci mogą nieumyślnie spowolnić działanie. Nawet jeśli wydaje się to oczywiste. Naprawdę oczywiste.

Nie wierzysz mi w tym drugim punkcie?

Jak uzyskać lepsze wyniki ze sprzętu o wartości 1400 USD niż ze sprzętu o wartości 7000 USD?

Jeff Atwood ze sławy StackOverflow zauważył kiedyś, że czasami (zazwyczaj, jego zdaniem) może być bardziej opłacalne po prostu kupić lepszy sprzęt niż poświęcać cenny czas programisty na optymalizację. OK, więc załóżmy, że doszedłeś do dość obiektywnego wniosku, że Twój projekt pasuje do tego scenariusza. Załóżmy dalej, że to, co próbujesz zoptymalizować, to czas kompilacji, ponieważ jest to potężny projekt Swift, nad którym pracujesz, a to stało się dość dużym wąskim gardłem dla programistów. Czas na zakupy sprzętu!

Co kupić? Cóż, oczywiście, jen za jena, droższy sprzęt zwykle działa lepiej niż tańszy sprzęt. Oczywiście Mac Pro o wartości 7 000 USD powinien kompilować oprogramowanie szybciej niż komputer Mac Mini ze średniej półki, prawda?

Zło!

Okazuje się, że czasem więcej rdzeni oznacza wydajniejszą kompilację… i w tym konkretnym przypadku LinkedIn przekonał się na własnej skórze, że w przypadku ich stosu jest odwrotnie.

Ale widziałem kierownictwo, które popełniło jeszcze jeden błąd: nie przeprowadzali nawet testów porównawczych przed i po, i odkryli, że aktualizacja sprzętu nie sprawiła, że ​​ich oprogramowanie „wyczuwało się” szybciej. Ale nie było sposobu, aby wiedzieć na pewno; co więcej, nadal nie mieli pojęcia, gdzie jest wąskie gardło, więc nie byli zadowoleni z wydajności, ponieważ zużyli czas i pieniądze, które byli skłonni przeznaczyć na rozwiązanie problemu.

OK, przeprowadziłem już testy porównawcze. Czy faktycznie mogę jeszcze zoptymalizować?

Tak, zakładając, że zdecydowałeś, że musisz. Ale może ta decyzja poczeka, aż więcej/wszystkie inne algorytmy również zostaną zaimplementowane, dzięki czemu możesz zobaczyć, jak ruchome części do siebie pasują i które są najważniejsze poprzez profilowanie. Może to być na poziomie aplikacji dla małej aplikacji lub może dotyczyć tylko jednego podsystemu. Tak czy inaczej, pamiętaj, że określony algorytm może wydawać się ważny dla całej aplikacji, ale nawet eksperci — zwłaszcza eksperci — są podatni na błędne diagnozowanie tego problemu.

Pomyśl, zanim się zakłócisz

„Nie wiem jak wy ludzie, ale…”

Gavin Belson z Doliny Krzemowej mówi: „Nie chcę żyć w świecie, w którym ktoś inny czyni świat lepszym… lepszym niż my”.

Na koniec zastanów się, w jaki sposób możesz zastosować ideę fałszywej optymalizacji w znacznie szerszym ujęciu: projektu lub samej firmy, a nawet sektora gospodarki.

Wiem, kuszące jest myśleć, że technologia uratuje sytuację i że możemy być bohaterami, którzy to umożliwią.

Plus, jeśli tego nie zrobimy, zrobi to ktoś inny.

Ale pamiętaj, że władza psuje pomimo najlepszych intencji. Nie będę tutaj linkował do żadnych konkretnych artykułów, ale jeśli nie wędrowałeś po żadnym, warto poszukać informacji o szerszym wpływie zakłócenia gospodarki i komu to ostatecznie służy. Możesz być zaskoczony niektórymi skutkami ubocznymi prób ratowania świata poprzez optymalizację.

Postscriptum

Zauważyłeś coś, Optimus? Jedyny raz, kiedy nazwałem Cię Optimus, był na początku i teraz na końcu. Nie nazywałeś się Optimus w całym artykule. Będę szczery, zapomniałem. Cały artykuł napisałem, nie nazywając Cię Optimusem. Na koniec, kiedy zdałem sobie sprawę, że powinienem wrócić i posypać twoim imieniem cały tekst, cichy głosik we mnie powiedział : nie rób tego .