Python w obliczeniach

Python, którego pierwsza wersja ukazała się w roku 1990 jest językiem stosunkowo młodym, jeśli porównać go z Fortranem (pochodzącym z lat 50) czy C (lata 70). Być może dlatego często spotyka się z krytyką dotyczącą jego użyteczności w wysoko wydajnych aplikacjach obliczeniowych. W poście, odnosząc się do najczęstszych zarzutów stawianych Pythonowi postaram się pokazać, że zarzuty te nie są obiektywne a przynajmniej, że nie są ,,gwoździem do trumny'' Pythona.

Zarzut 1 - Python jest mało wydajny

Zarzut ten jest chyba najczęstszym zarzutem i najpoważniejszym. Testy języków programowania w projekcie TCLBG pokazują, że Python w programach o tej samej funkcjonalności jest 15-25 razy wolniejszy niż C, Java czy Fortran. Czy to przesądza o Pythonie?

Projekt TCLBG porównuje wydajność języków programowania bez żadnych bibliotek. Jest to więc porównanie wydajności działania standardowych typów danych i konstrukcji składniowych. Tymczasem w rzeczywistych zastosowaniach niemal zawsze stosuje się gotowe biblioteki, służące do rozwiązywania specyficznych problemów. Przykładowo porównanie to wskazuje jak wydajna byłaby procedura wykonująca mnożenie macierzowe napisana przez nas. Tymczasem w zastosowaniach HPC (ang. High Performance Computing) podstawową zasadą jest korzystanie ze sprawdzonych, dopracowanych rozwiązań (w przypadku mnożenia macierzy - np. biblioteki BLAS). Jeżeli weźmiemy to pod uwagę oraz to, że Python posiada wrappery do bibliotek takich jak BLAS czy LAPACK (i dziesiątek jeżeli nie setek innych), napisanych w Fortranie i w C to argument o wydajności przestaje być taki celny.

W zastosowaniach numerycznych pewnie z 99.99%-99.999% pętli programu wykonywanych jest wewnątrz funkcji matematycznych (jak mnożenia macierzy, całkowania itd.), więc wydajność 0.01%-0.001% pętli nie odgrywa istotnej roli.

Standardowe typy danych także nie są przeszkodą w wydajności. Standardowa biblioteka numeryczna Pythona - numpy posiada wydajną implementację wielu potrezbnych struktur danych - tablic wielowymiarowych, macierzy itd. wraz z niezwykle obszernym zestawem funkcji do manipulowania nimi. Wszystko z implementacją w C, dla wydajności.

Osoby, którym szczególnie doskwiera wydajność natywnych pętli w Pythonie pomóc może Cython - implementacja Pythona w C, która umożliwia kompilowanie kodu a jej wydajność nie odstępuje znacząco od C, kosztem nieco mniejszej przenośności i przejrzystości kodu (przynajmniej w moim odczuciu).

Zarzut 2 - Nikt nie używa Pythona, a wszyscy używają XYZ

Do obliczeń numerycznych używam Pythona ja - co w zasadzie obala twierdzenie, ale rozwinę nieco ten problem:)

Dzięki temu, że nie jest to rozwojowa wersja języka programowania z komputerów lampowych ,,karmionych'' kartami perforowanymi Python jest językiem nowoczesnym. Od swojego początku silnie zorientowany na obiektowość, prostotę i czytelność kodu szybko zdobył popularność także w ośrodkach naukowych. Wycinek listy naukowych zastosowań Pythona (w których uczestniczą także najlepsze uniwersystety i ośrodki obliczeniowe) można znaleźć na stronach biblioteki scipy. Innym przykładem jest aplikacja GPAW, rozwiązująca problemy teorii funkcjonału gęstości Kohna-Shama m.in. na superkomputerach o architekturze Blue Gene, a napisana w 90% w Pythonie.

Dużą zaletą wynikającą z ogólnego przeznaczenia Pythona jest to, że oprócz naukowców używa go mnóstwo programistów aplikacji zarówno ,,biurkowych'' jak i webowych. Dzięki temu ma on bardzo dobrą dokumentację, wsparcie i kompetentne grupy użytkowników. Posiada biblioteki, które pozwalają na stosowanie dowolnych niemal algorytmów, technik programowania i wykorzystywać narzędzia sieciowe, bazodanowe, tworzyć interfejsy graficzne itd. Daje to programiście większą swobodę niż w przypadku Fortrana (gdzie nawet najprostszy interfejs graficzny jest w praktyce nie do zrealizowania), jednak bez niskopoziomowego ,,grzebania'', z jakim natkniemy się pisząc program w C lub C++.

Zarzut 3 - Język XYZ ma dojrzałe biblioteki, a Python nie

Osoby, które stawiają taki zarzut powinny zapoznać się np. z dokumentacją biblioteki numpy i scipy (do czego gorąco każdego zachęcam). W Pythonie można bardzo łatwo korzystać z kodu napisanego w innych językach programowania, więc tym bardziej zarzut ten uważam za słaby. Trudno mi podać przykład funkcjonalności, do której nie ma biblioteki w Pythonie (nie mówię o całych aplikacjach), zachęcam do podawania takich przykładów. Tymczasem jest wiele bibliotek, gdzie ma miejsce przeciwna sytuacja: są w Pythonie, a w XYZ nie ma (np. biblioteki do wielowątkowości i wieloprocesowości, bazodanowe, graficzne itd.), z dodatkowym bonusem w postaci czytelności obiektowego kodu.

Zarzut 4 - Język XYZ jest przenośny, a Python nie

Stoję na stanowisku, że nie ma naprawdę przenośnych języków programowania. Każdy spotyka się z problemem specyficzności platformy - Java działa różnie na różnych maszynach Javy, podobnie jest z JavaScriptem, C uważany jest już powoli za niskopoziomowy język programowania, bo nawet typy danych zależą od konkretnej platformy, w wiele funkcjonalności wymaga odwoływania się do wywołań systemowych, co jest z natury zależne od tego systemu. Programy w Fortranie działają różnie w zależności od tego, jakim kompilatorem zostały skompilowane, z jakimi wersjami bibliotek i z jakimi przełącznikami kompilacji (a różnice te mogą być od małych przez znaczące aż po całkowicie błędne działanie programu). Python jest tak przenośny, jak przenośne są biblioteki których chcemy użyć (bo sam język działa wszędzie: interpreter mam zainstalowany jako wtyczkę do Google Chrome, na telefonie komórkowym Orange San Francisco z Androidem 2.1 dzięki Android-Scripting, pod Windowsem i Linuxami różnych dystrybucji oraz używam Pythona w Google App Engine). Biblioteki zaś są zwykle Open Source'owe, więc w teorii skompilować także możemy je wszędzie.

Zarzut 5 - Python ma dziwaczną składnię

W zasadzie to inne języki mają dziwaczną składnię:) De gustibus non est disputandum.

Indentacje w Pythonie są po prostu wymuszeniem i tak standardowego sposobu poprawiania czytelności kodu. Niestosowanie indentacji jest przykładem złych praktyk programistycznych. Brak klamer/nawiasów wynika stąd, że kod powinien być czytelny dla człowieka, a liczenie klamer by sprawdzić czy już zamknęliśmy pętlę czy jeszcze nie nikogo chyba nie bawi. Klamry obecne były aby ułatwić pracę programistom kompilatorów (tokenizera w kompilatorze jeśli dobrze rozumiem), co i tak niewiele im dało. Słowa kluczowe znane np. z Fortrana, zamykające instrukcje sterujące przepływem kodu (END blablabla) zwiększają czytelność tylko, gdy nie mamy żadnych zagnieżdżeń takowych instrukcji. Gdy takie zagnieżdżenia są, to bez indentacji ani rusz.

Zarzut 6 - Python nie wymaga definicji zmiennych ani deklaracji ich typów

Wymóg definiowania zmiennych i deklaracji ich typów jest narzędziem bezpieczeństwa, które ma pozwolić uniknąć programiście błędów.

Wymóg określenia typu zmiennej nie uchroni programisty przed przypadkowym nadpisaniem zmiennej, gdyż to najczęściej zdarza się przy pomocy zmiennych tych samych typów.

Definiowanie zmiennych pozwala zaś programiście uniknąć błędu korzystania z nieistniejących zmiennych. Także to niezbyt wiele daje: większość języków prograwania wymagających definiowania zmiennych nie wymusza ich inicjowania, więc łatwo (i często) popełnia się błąd korzystania z zmiennej o nieokreślonej wartości mimo, że jest ona zadeklarowana.

Znacznie lepszym mechanizmem bezpieczeństwa jest stosowanie przestrzeni nazw, które zabezpieczają przez nadpisywaniem zmiennych, stosowanie obiektowości i unikanie zmiennych globalnych. Przed wykorzystaniem nieistniejącej zmiennej Python zabezpiecza całkowicie: zmienna nie może być zainicjowana po prawej stronie operatora przypisania, tj. operacja a=b-1 zakończy się niepowodzeniem, jeżeli wcześniej nie przypisaliśmy zmiennej b jakiejś wartości.

Drugim aspektem (ważniejszym) wymagania deklarowania typów zmiennych jest wydajność zarówno dot. wykorzystania pamięci jak i samego wykonania kodu. Gdyby Python zupełnie był pozbawiony typów, mogłoby to być problemem. Jeżeli jednak stosujemy biblioteki numeryczne do reprezentacji wektorów i tablic, to ich elementy posiadają typ, zgodny z wszystkimi typami standardowymi C i Fortran. Typ ten możemy ustalić lub też korzystać z domyślnego (opisanego w dokumentacji biblioteki). W przypadku zaś zmiennych, na których nie wykonujemy milionów operacji na sekundę deklarowanie typu zmiennej nie jest problemem wydajnościowym.

Ostatni z aspektów który uważam za minus typowania zmiennych jest konieczność definiowania polimorficznych funkcji, jeżeli chcemy wykorzystać naszą funkcję do innego typu zmiennej. Wymusza to albo powielanie kodu (co jest złą praktyką) albo stosowanie skomplikowanych struktur składniowych (w C) lub też wreszcie używanie ,,na zapas'' najwiekszych możliwie potrzebnych typów danych (np. complex*16). Nie uważam, aby było to dobre rozwiązanie.

 

Podsumowanie

Zarzuty stawiane Pythonowi z którymi się spotkałem z reguły nie są trudne do odparcia. Mam nadzieję, że komuś przyda się powyższy post:) Jeżeli ktoś ma jakieś zarzuty wobec Pythona, to proszę je podawać: myślę że dyskusja będzie wartościowa:)