Czy komuś kiedyś zawiesił się komputer?
Retorycznie pytanie o szybkość komputerów na wstępie. Takie pytanie może wywołać falę narzekania albo nawet długą opowieść typu “za moich czasów, to dopiero było!”. Każdy chyba zna to uczucie, kiedy coś w maszynie “przymula”. Gdy oglądany serial co chwilę zacina się, aktualizacja trwa w nieskończoność, restart zajmuje więcej czasu niż partia szachów. A z drugiej strony, ci sami narzekający co jakiś czas dowiadują się o kolejnej nowince technicznej, która jest w jakiś sposób szybsza. Jeszcze więcej cykli procesora na sekundę, jeszcze krótszy czas dostępu do pamięci, jeszcze większy transfer danych. Jak to możliwe, że te dwa zjawiska występują równocześnie?
Szybkość komputerów: sprzęt – szybszy, działanie – niezbyt
To prawda, że sprzęt komputerowy przechodzi dynamiczny rozwój. W zasadzie jest on aż trudny do przyjęcia, jeśli porównać go z postępem w innych dziedzinach. W 2000 r. pojawiły się pierwsze komercyjne gigahercowe CPU (koszt: ponad 1000 $), a w 2020 r. Intel wypuścił na rynek 20-wątkowy procesor o maksymalnej częstotliwości taktowania 5,3 GHz (koszt: ok. 500 $). Oznacza to teoretycznie 106-krotne przyspieszenie (20*5,3 GHz/1 GHz). Gdyby porównać to z mocą silników spalinowych, to otrzymamy przeskok z napędu awionetki na Titanica.
Czy zatem szybkość komputerów nowych jest większa, niż starych?
Ale wniosek, że nowy komputer (z górnej półki) działa 100 razy szybciej niż 20 lat temu, jest całkowicie błędny. Strony WWW jakoś nie ładują się o tyle krócej, uruchomienie systemu nie trwa jednej sekundy. Co sprawia, że nie czujemy tego imponującego rozwoju? Najprostsze wytłumaczenie nasuwa się pewnie dość łatwo: przez te lata wzrosła nie tylko szybkość komputerów i ich możliwości, ale i wymagania wobec nich. To prawda, ale nie cała!
Skąd się biorą wymagania wobec komputerów?
Odpowiedź brzmi: od użytkowników! W sporej części nie zdajemy sobie z tego sprawy, ale nasze żądania stawiane komputerom nieustannie rosną. W Windowsie 98 można było ustawić 256 kolorów monitora, co przy powszechnej wówczas rozdzielczości ekranu 1024 × 768 i odświeżaniu 50 Hz daje transfer 37,5 MB/s. Obecnie monitory FullHD stały się standardem, a wymagają 395,5 MB/s. To oczywiście uproszczenia i nie można powiedzieć, że komputery mają nieustannie takie obciążenie tylko dlatego, że monitor jest podłączony. Daje to jednak wyobrażenie na temat pojęcia “norma”. Stare interfejsy, strony internetowe, grafika w grach – oglądane współcześnie, rażą swoim wyglądem. Tak bardzo odstaje on od tego, do czego przyzwyczajamy się niezauważenie. Mało kto zdaje sobie naprawdę sprawę, ile wymogów mają coraz to nowe programy.
Co z odpowiedziami na wymagania użytkowników wobec komputerów?
Dlatego właśnie wciąż trwa wyścig technologiczny po szybsze procesory, pamięci, łącza, interfejsy. Konstruowanie lepszego sprzętu wiąże się z wielkimi kosztami, a jednak musi być opłacalne i to na wielką skalę. Społeczeństwo musi przyzwyczaić się, a następnie znużyć aktualnie przyjętą nowinką, aby warto było wynajdywać następną. Tak było z dyskietkami, CD-ROM, DVD, Blu-ray. Żadna z tych rzeczy nie powstałaby, gdyby nie rosnące wymagania użytkowników wobec komputerów. Jak to wygląda w szczegółach? Żeby na to odpowiedzieć, trzeba umieć spojrzeć na programy od zaplecza – z punktu widzenia programisty.
Co może zdziałać developer?
Nawiązując do kwestii wymagań: każdy projekt powinien mieścić się w pewnych granicach, jeśli chodzi o zużycie zasobów komputera. To podstawowe zagadnienie, które uwzględnia się np. w Scrumie na cyklicznych planowaniach. Jednak fantastyczny rozwój sprzętu sprawił, że tam, gdzie dawniej trzeba było uważać na każdy zajęty kilobajt, teraz pamięć jest niemal nieograniczona. Gdzie można było spodziewać się opóźnień, obliczenia teraz prawie wcale nie zajmują czasu. Powoli zmieniało to sposób myślenia przeciętnego programisty, od wielkiej skrupulatności do lekkomyślności.
Dlaczego zwinność? Scrum i inne metodyki zwinne
Obie te skrajności nie wróżą nic dobrego! Pierwsza powoduje, że projekt przeciąga się ponad miarę. To powoduje wymierne biznesowe straty. Często projekt lepiej jest wypuścić niedopracowany niż mocno opóźniony. Znamy to choćby z gier komputerowych, które wymagają łatania już po premierze. Druga skrajność sprawia, że program działający z początku sprawnie, z czasem zaczyna wymagać nieproporcjonalnie dużo.
Jakie są następstwa decyzji programistów?
Pochopne decyzje techniczne podjęte na początku mogą mieć trudne do przewidzenia konsekwencje. Odpowiedzialny programista zawsze powinien mieć świadomość, jak często podejmuje różne wybory oraz jakie są ich następstwa. Poniżej kilka przykładów:
Język programowania
Choć nie ma tu reguły, do szybkiego prototypowania lub tworzenia niedużych aplikacji nadają się języki skryptowe, np. Python, JavaScript lub też Ruby. Da się w nich również tworzyć wydajne, duże aplikacje. Nie bez powodu jednak dość powszechnie uznaje się, że programy pisane w C i C++ mogą zostać bardziej zoptymalizowane. Oznacza to po prostu możliwość lepszego wykorzystania tych samych zasobów.
Czysty kod
Jedno z pierwszych mniej przyjemnych doświadczeń początkującego developera brzmi: “nie pamiętam, o co chodzi w moim własnym kodzie”. Kiedy po jakimś czasie trzeba cokolwiek zmienić lub ulepszyć, często okazuje się to zaskakująco trudne. Techniki zwane łącznie “clean code”, popularyzowane m.in. przez Roberta C. Martina, ułatwiają tworzenie łatwo utrzymywalnych programów. To oznacza, że wprowadzanie poprawek nie wywołuje frustracji i nie wiąże się z mozolnym przerabianiem.
Wielowątkowość
W większych aplikacjach naturalne jest skorzystanie z dostępności kilku rdzeni CPU. Niestety nie każde zagadnienie da się rozłożyć na kilka niezależnych, równolegle wykonywanych zadań. Co więcej, wątki często potrzebują się ze sobą komunikować – to również wpływa na wydajność. Dlatego zdarza się, że wbrew pozorom szybciej jest przeprowadzać obliczenia na jednym wątku. Po prostu nie należy przesądzać o tym z góry.
Właściwa optymalizacja.
Technik na przyspieszenie programu jest mnóstwo. Zasadnicze pytanie brzmi: jak się za to zabrać? Otóż warto zacząć od wyznaczenia wąskich gardeł (ang. bottlenecks). To części programu (zazwyczaj niewielkie), których wykonanie trwa najdłużej lub które zajmują najwięcej pamięci. Warto mieć na podorędziu testy – benchmarki, które zmierzą zużycie zasobów i pokażą, czy optymalizacja w danym miejscu w ogóle ma rację bytu.
Dynamiczne alokacje pamięci.
Jej częste zajmowanie i zwalnianie potrafi znacznie spowolnić działanie programu i znacznie wpłynąć na szybkość komputerów. Niejeden język programowania dokonuje ich niejawnie, tzn. bez wyraźnego zaznaczenia tego w kodzie. Odciąża to programistę z konieczności zarządzania pamięcią. Dzięki temu łatwiej jest skupić się na implementacji. Niestety, kiedy aplikacja rozrasta się, mogą pojawić się problemy z zajmowaniem RAM-u. Jeśli od początku wcale się tym nie przejmowano, optymalizacja może nawet wymagać przepisania całości od nowa.
Kontenery
Czyli struktury do przechowywania danych, mają duże znaczenie dla szybkości programu. Jedne mają mały narzut pamięci, inne pozwalają na szybkie wstawianie i usuwanie elementów, jeszcze inne wydajnie wyszukują informacje. Ale nie da się mieć wszystkich zalet naraz. Warto więc czasem poświęcić czas na wybór tego właściwego rozwiązania.
Istnieje zatem spore pole do popisu dla tworzenia szybkich, wydajnych programów. Jednak jakoś nie zawsze się to udaje. Musi być więc jakiś rozdźwięk między teorią a praktyką.
Co faktycznie zdziała developer
Powyższe punkty faktycznie przedstawiają możliwości programistów, ale tylko z punktu widzenia pisania kodu. Jest jeszcze druga strona, czyli… samo życie. Ograniczone są nie tylko procesory, RAM i dyski, lecz również:
Termin oddania projektu
Czy jest to projekt w szkole programowania, czy w pracy – istnieją deadline’y. Żeby przyspieszyć pracę, każdy czasem idzie na skróty. Niekiedy owocuje to np. sprytnym wykorzystaniem już istniejącego rozwiązania, a kiedy indziej – pośpiechem i bałaganem w kodzie. A to powoduje wzrost długu technicznego i często wolniej działającą aplikację!
Doświadczenie
Jak wspomniano, istnieje mnóstwo sposobów na problemy, które ktoś miał już wcześniej. Czy to krótka odpowiedź na StackOverflow, czy wydajna biblioteka, ułatwiająca potrzebne obliczenia – Internet może być bardzo pomocny w poszukiwaniach. Nie zastąpi jednak rzetelnej wiedzy, bo zadanie trzeba przecież namierzyć, nazwać i wybrać dobre rozwiązanie. O ileż lepiej, kiedy po prostu się je zna!
Koordynacja pracy w zespole
Współpraca w kilka osób to chleb powszedni, wiele powstało metodyk jej ulepszania – można tu podciągnąć większość słynnego Agile. Zgodność, właściwy podział zadań i regularne ich omawianie są kluczowe, aby postęp szedł gładko i prowadził do dobrego wykorzystania zasobów przez program.
Możliwości umysłowe
To prawdopodobnie najważniejszy punkt, gdyż nikt z nas nie jest ideałem. W naszym świecie błędy nazywa się konkretnie bugami i praktycznie żaden program nie jest od nich wolny. Dobry developer jest pogodzony z faktem, że je robi. Przejście przez popełnienie poważniejszych pomyłek to dobra nauka. Ważne jest, aby nie przeceniać swoich sił i ufać raczej rzetelnie zrobionym testom niż własnemu poczuciu nieomylności.
Podsumowanie
Przejście od rozwoju sprzętu komputerowego do wydajności umysłowej przeciętnego programisty okazało się krótsze, niż można by się spodziewać. Koniec końców pojęcie “szybkość komputerów” jest wrażeniem użytkownika końcowego. Opiera się na mnóstwie czynników – zarówno sprzętowych, softowych, jak i czysto ludzkich. Warto mieć je wszystkie na uwadze w codziennej pracy. Ktoś powie, że to zbyt dużo naraz? Cóż, nikt nie mówił, że ta robota zawsze jest lekka! Spokojnie i po kolei, doświadczenie przychodzi z czasem. A skoro i tak wszyscy popełniamy błędy, to nie ma co się ich wstydzić. Tylko poprawić – i będzie kolejna wersja!