Zastanawiam się, czy można zbudować kompilatory dla dynamicznych języków, takich jak Ruby, aby mieć podobną i porównywalną wydajność do C / C ++? Z tego, co rozumiem na temat kompilatorów, weźmy na przykład Ruby, kompilowanie kodu Ruby nigdy nie będzie wydajne, ponieważ sposób, w jaki Ruby obsługuje odbicie, funkcje takie jak automatyczna konwersja typów z liczb całkowitych na duże liczby całkowite oraz brak pisania statycznego sprawia, że budowanie wydajnego kompilatora dla Ruby niezwykle trudne.
Czy można zbudować kompilator, który może kompilować Ruby lub inne dynamiczne języki do pliku binarnego, który działa bardzo blisko C / C ++? Czy istnieje fundamentalny powód, dla którego kompilatory JIT, takie jak PyPy / Rubinius, ostatecznie lub nigdy nie będą pasować do C / C ++ pod względem wydajności?
Uwaga: Rozumiem, że „wydajność” może być niejasna, więc aby to wyjaśnić, miałem na myśli, że jeśli możesz zrobić X w C / C ++ z wydajnością Y, czy możesz zrobić X w Ruby / Python z wydajnością zbliżoną do Y? Gdzie X to wszystko, od sterowników urządzeń i kodu systemu operacyjnego, po aplikacje internetowe.
źródło
Odpowiedzi:
Wszystkim, którzy powiedzieli „tak”, zaoferuję kontrapunkt, że odpowiedź brzmi „nie” z założenia . Języki te nigdy nie będą w stanie dopasować wydajności języków skompilowanych statycznie.
Kos zaoferował (bardzo prawidłowy) punkt, w którym dynamiczne języki mają więcej informacji o systemie w czasie wykonywania, które można wykorzystać do optymalizacji kodu.
Istnieje jednak druga strona medalu: należy śledzić te dodatkowe informacje. W nowoczesnych architekturach jest to zabójca wydajności.
William Edwards oferuje ładny przegląd argumentu .
W szczególności wspomniane przez Kos optymalizacje nie mogą być stosowane poza bardzo ograniczonym zakresem, chyba że drastycznie ograniczysz moc ekspresji swoich języków, jak wspomniał Devin. Jest to oczywiście opłacalny kompromis, ale ze względu na dyskusję powstaje język statyczny , a nie dynamiczny. Te języki różnią się zasadniczo od Python lub Ruby, ponieważ większość ludzi je rozumie.
William przytacza kilka interesujących slajdów IBM :
Niektóre z tych kontroli można wyeliminować po analizie (uwaga: ta analiza wymaga również czasu - w czasie wykonywania).
Ponadto Kos twierdzi, że języki dynamiczne mogą nawet przewyższyć wydajność C ++. JIT może rzeczywiście analizować zachowanie programu i zastosować odpowiednie optymalizacje.
Ale kompilatory C ++ mogą zrobić to samo! Nowoczesne kompilatory oferują tak zwaną optymalizację sterowaną profilem, która, jeśli otrzyma odpowiednie dane wejściowe, może modelować zachowanie środowiska wykonawczego programu i zastosować te same optymalizacje, które zastosowałby JIT.
Oczywiście wszystko zależy od istnienia realistycznych danych treningowych, a ponadto program nie może dostosować swoich charakterystyk wykonawczych, jeśli wzorzec użytkowania zmieni się w połowie cyklu. JIT teoretycznie sobie z tym poradzą. Byłbym zainteresowany, aby zobaczyć, jak to wygląda w praktyce, ponieważ w celu przełączenia optymalizacji JIT musiałby stale zbierać dane o użytkowaniu, co ponownie spowalnia wykonanie.
Podsumowując, nie jestem przekonany, że optymalizacje w punkcie startowym środowiska uruchomieniowego przeważają nad kosztem śledzenia informacji o środowisku wykonawczym w długim okresie , w porównaniu do analizy statycznej i optymalizacji.
źródło
javac
kiedykolwiek była przeprowadzana optymalizacja profilowa? Nie, o ile mi wiadomo. Zasadniczo nie ma sensu, aby kompilator języka JIT był dobrze zoptymalizowany, ponieważ JIT może sobie z tym poradzić (a przynajmniej w ten sposób więcej języków korzysta z tego wysiłku). Tak więc (co zrozumiałe)javac
optymalizator nigdy nie wkładał wiele wysiłku , o ile wiem (w przypadku języków .NET jest to zdecydowanie prawda).Tak. Weźmy jako przykład PyPy. Jest to zbiór kodu Pythona, który wykonuje interpretację zbliżoną do C (nie tak blisko, ale też nie aż tak daleko). Dokonuje tego, przeprowadzając analizę całego programu w kodzie źródłowym, aby przypisać każdej zmiennej typ statyczny (szczegółowe informacje znajdują się w dokumentach Annotator i Rtyper ), a następnie, po uzbrojeniu w te same informacje o typie, co C, można wykonać to samo rodzaje optymalizacji. Przynajmniej w teorii.
Kompromis oczywiście polega na tym, że RPython akceptuje tylko podzbiór kodu Pythona i ogólnie, nawet jeśli to ograniczenie zostanie zniesione, tylko podzbiór kodu Pythona może sobie poradzić: podzbiór, który można analizować i nadać typy statyczne.
Jeśli wystarczająco ograniczysz Python, możesz zbudować optymalizatory, które będą mogły skorzystać z ograniczonego podzbioru i skompilować go do wydajnego kodu. To naprawdę nie jest interesująca korzyść, w rzeczywistości jest dobrze znana. Ale głównym celem używania Pythona (lub Ruby) było przede wszystkim to, że chcieliśmy zastosować ciekawe funkcje, które być może nie analizują dobrze i dają dobrą wydajność! Ciekawe pytanie brzmi ...
Nie
Rozumiem przez to: pewnie, że w miarę gromadzenia się kodu można uzyskać wystarczającą ilość informacji o pisaniu i wystarczającą liczbę punktów aktywnych do skompilowania całego kodu aż do kodu maszynowego. I może uda nam się uzyskać lepszą wydajność niż C dla jakiegoś kodu. Nie sądzę, żeby to było bardzo kontrowersyjne. Ale wciąż musi się „rozgrzać”, a wydajność jest nieco mniej przewidywalna i nie będzie tak dobra jak C lub C ++ dla niektórych zadań, które wymagają konsekwentnie i przewidywalnie wysokiej wydajności.
Istniejące dane dotyczące wydajności dla języka Java, które zawierają zarówno więcej informacji o typie niż Python lub Ruby, jak i lepiej rozwinięty kompilator JIT niż Python lub Ruby, nadal nie są zgodne z C / C ++. Jest jednak na tym samym boisku.
źródło
Krótka odpowiedź brzmi: nie wiemy , zapytaj ponownie za 100 lat. (Być może nadal nie będziemy wiedzieć; być może nigdy się nie dowiemy.)
Teoretycznie jest to możliwe. Weź wszystkie napisane programy, ręcznie przetłumacz je na najbardziej wydajny kod maszynowy i napisz interpreter, który mapuje kody źródłowe na kody maszynowe. Jest to możliwe, ponieważ kiedykolwiek napisano tylko skończoną liczbę programów (a gdy więcej programów zostanie napisanych, nie przestawaj tłumaczeń ręcznych). Jest to oczywiście całkowicie idiotyczne pod względem praktycznym.
Z drugiej strony teoretycznie języki wysokiego poziomu mogą osiągnąć wydajność kodu maszynowego, ale go nie przekroczą. Jest to nadal bardzo teoretyczne, ponieważ w praktyce bardzo rzadko uciekamy się do pisania kodu maszynowego. Ten argument nie dotyczy porównywania języków wyższego poziomu: nie oznacza, że C musi być bardziej wydajny niż Python, tylko ten kod maszynowy nie może być gorszy niż Python.
Z drugiej strony, na czysto eksperymentalnych warunkach, widzimy, że w większości przypadków interpretowane języki wysokiego poziomu osiągają gorsze wyniki niż skompilowane języki niskiego poziomu. Mamy tendencję do pisania kodu, który nie jest wrażliwy na czas, w językach wysokiego poziomu i krytycznych czasowo wewnętrznych pętli w asemblerze, z językami takimi jak C i Python pomiędzy nimi. Chociaż nie mam żadnych statystyk, które mogłyby to potwierdzić, myślę, że w większości przypadków jest to najlepsza decyzja.
Istnieją jednak niekwestionowane przypadki, w których języki wysokiego poziomu pokonują kod, który można realistycznie napisać: środowiska programowania specjalnego przeznaczenia. Programy takie jak Matlab i Mathematica są często znacznie lepsze w rozwiązywaniu niektórych problemów matematycznych niż to, co potrafią napisać zwykli śmiertelnicy. Funkcje biblioteki mogły być napisane w C lub C ++ (co jest paliwem do obozu „języki niskiego poziomu są bardziej wydajne”), ale to nie moja sprawa, jeśli piszę kod Mathematica, biblioteka to czarna skrzynka.
Czy teoretycznie jest możliwe, że Python zbliży się, a może nawet, do optymalnej wydajności niż C? Jak widać powyżej, tak, ale dzisiaj jesteśmy bardzo daleko od tego. Z drugiej strony, kompilatory poczyniły duży postęp w ciągu ostatnich dziesięcioleci i postęp ten nie zwalnia.
Języki wysokiego poziomu powodują, że więcej rzeczy jest zautomatyzowanych, więc mają więcej pracy do wykonania, a zatem są mniej wydajne. Z drugiej strony mają one zwykle więcej informacji semantycznych, więc łatwiej jest dostrzec optymalizacje (jeśli piszesz kompilator Haskell, nie musisz się martwić, że inny wątek zmodyfikuje zmienną pod twoim nosem). Jednym z kilku wysiłków zmierzających do porównania
jabłek i pomarańczy wróżnych językach programowania jest gra Computer Language Benchmark Game (wcześniej znana jako strzelanka). Fortran błyszczy w zadaniach numerycznych; ale jeśli chodzi o manipulowanie danymi strukturalnymi lub komutacją szybkich wątków, F # i Scala mają się dobrze. Nie bierz tych wyników za ewangelię: wiele z tego, co mierzą, to to, jak dobry był autor programu testowego w każdym języku.Argumentem przemawiającym za językami wysokiego poziomu jest to, że wydajność w nowoczesnych systemach nie jest tak silnie skorelowana z liczbą wykonywanych instrukcji, a tym bardziej z czasem. Języki niskiego poziomu są odpowiednie dla prostych maszyn sekwencyjnych. Jeśli język wysokiego poziomu wykonuje dwukrotnie więcej instrukcji, ale bardziej inteligentnie korzysta z pamięci podręcznej, dzięki czemu pomija o połowę mniej pamięci podręcznej, może wygrać.
Na platformach serwerowych i stacjonarnych procesory prawie osiągnęły płaskowyż, w którym nie przyspieszają (platformy mobilne też się tam dostają); faworyzuje to języki, w których równoległość jest łatwa do wykorzystania. Wiele procesorów spędza większość czasu na oczekiwaniu na odpowiedź We / Wy; czas spędzony na obliczeniach ma niewielkie znaczenie w porównaniu z ilością operacji we / wy, a język, który pozwala programiście zminimalizować komunikację, jest korzystny.
Podsumowując, podczas gdy języki wysokiego poziomu zaczynają się od kary, mają więcej miejsca na ulepszenia. Jak blisko mogą się zbliżyć? Zapytaj ponownie za 100 lat.
Uwaga końcowa: często nie dokonuje się porównania między najbardziej wydajnym programem, który można napisać w języku A i tym samym w języku B, ani między najskuteczniejszym programem, jaki kiedykolwiek napisano w każdym języku, ale między najskuteczniejszym programem, który można napisać przez człowieka w określonym czasie w każdym języku. Wprowadza to element, którego nie można analizować matematycznie, nawet w zasadzie. W praktyce oznacza to często, że najlepsza wydajność to kompromis między tym, ile kodu niskiego poziomu trzeba napisać, aby osiągnąć cele wydajności, a tym, ile kodu niskiego poziomu trzeba napisać, aby dotrzymać dat wydania.
źródło
Podstawowa różnica pomiędzy ++ rachunku C
x = a + b
oraz sprawozdania Pythonx = a + b
jest to, że C / C ++ kompilator może powiedzieć z tym stwierdzeniem (i trochę dodatkowych informacji, że ma łatwo dostępne o typachx
,a
ib
) właśnie maszyna kod musi być wykonywany . Podczas gdy, aby powiedzieć, jakie operacje ma wykonać instrukcja Python, musisz rozwiązać problem zatrzymania.W języku C ta instrukcja po prostu skompiluje się do jednego z kilku rodzajów dodawania maszyn (a kompilator C wie, który). W C ++ może się kompilować w ten sposób lub może wywoływać funkcję wywoływania statycznie znanej funkcji lub (w najgorszym przypadku) może być zmuszona do kompilacji do wirtualnego wyszukiwania i wywoływania metod, ale nawet to ma dość niewielki narzut kodu maszynowego. Co ważniejsze, kompilator C ++ może stwierdzić na podstawie statycznie znanych typów, czy może wyemitować pojedynczą operację szybkiego dodawania, czy też musi użyć jednej z wolniejszych opcji.
W Pythonie, kompilator może teoretycznie zrobić prawie tak dobre, jeśli wiedział, że
a
ib
obajint
s. Istnieje pewne dodatkowe obciążenie związane z boksem, ale jeśli typy byłyby statycznie znane, prawdopodobnie można by się tego również pozbyć (wciąż przedstawiając interfejs, że liczby całkowite są obiektami z metodami, hierarchią superklas itp.). Problem polega na tym, że kompilator Pythona nie możewiedz o tym, ponieważ klasy są definiowane w czasie wykonywania, mogą być modyfikowane w czasie wykonywania, a nawet moduły, które definiują i importują, są rozwiązywane w czasie wykonywania (a nawet, które instrukcje importowania są wykonywane, zależą od rzeczy, które można poznać tylko w czasie wykonywania). Tak więc kompilator Pythona musiałby wiedzieć, jaki kod został wykonany (tj. Rozwiązać problem zatrzymania), aby wiedzieć, co zrobi skompilowana instrukcja.Tak więc nawet przy najbardziej wyrafinowanych analizach, które są teoretycznie możliwe , po prostu nie można wiele powiedzieć o tym, co dana instrukcja Python zrobi z wyprzedzeniem. Oznacza to, że nawet jeśli zaimplementowany zostałby wyrafinowany kompilator Pythona, prawie we wszystkich przypadkach nadal musiałby emitować kod maszynowy zgodny z protokołem wyszukiwania słownika Python, aby określić klasę obiektu i znaleźć metody (przechodząc przez MRO hierarchii klas, który może również zmieniać się dynamicznie w czasie wykonywania, a więc trudno go skompilować do prostej wirtualnej tabeli metod) i zasadniczo robić to, co robią (powolni) tłumacze. Dlatego tak naprawdę nie ma wyrafinowanych kompilatorów optymalizujących dla języków dynamicznych. Nie jest to tylko trudne do stworzenia, maksymalna możliwa wypłata nie jest
Zauważ, że to nie jest oparte na tym, co kod jest robić, to na podstawie tego, co kod mógłby robić. Nawet kod Pythona, który jest prostą serią liczb całkowitych operacji arytmetycznych, musi zostać skompilowany tak, jakby mógł wywoływać dowolne operacje klasowe. Języki statyczne mają większe ograniczenia dotyczące możliwości działania kodu, w związku z czym ich kompilatory mogą przyjmować więcej założeń.
Kompilatory JIT zyskują na tym, czekając, aż środowisko wykonawcze skompiluje / zoptymalizuje. To pozwala im emitować kod, który działa na co kod jest robić raczej niż to, co można było zrobić. Z tego powodu kompilatory JIT mają znacznie większą potencjalną korzyść dla języków dynamicznych niż dla języków statycznych; dla bardziej statycznych języków większość tego, co chciałby wiedzieć optymalizator, można poznać z wyprzedzeniem, więc równie dobrze możesz go zoptymalizować, pozostawiając mniej kompilatora JIT.
Istnieją różne kompilatory JIT dla języków dynamicznych, które twierdzą, że osiągają prędkości wykonania porównywalne z kompilowanymi i zoptymalizowanymi C / C ++. Istnieją nawet optymalizacje, które mogą być wykonywane przez kompilator JIT, których nie można wykonać za pomocą kompilatora z wyprzedzeniem dla dowolnego języka, więc teoretycznie kompilacja JIT (dla niektórych programów) może pewnego dnia przewyższyć najlepszy możliwy kompilator statyczny. Ale jak słusznie zauważył Devin, właściwości kompilacji JIT (tylko „hotspoty” są szybkie i dopiero po okresie rozgrzewki) oznaczają, że języki dynamiczne skompilowane w JIT raczej nie będą odpowiednie dla wszystkich możliwych aplikacji, nawet jeśli staną się tak szybkie lub szybsze niż języki skompilowane statycznie.
źródło
foo = x + y
którym przewidywanie zachowania operatora dodawania w czasie kompilacji zależy od rozwiązania problemu zatrzymania.x + y
do wydajnych operacji dodawania komputera, musisz wiedzieć w czasie kompilacji, czy tak jest. Przez cały czas , nie tylko przez jakiś czas. W przypadku języków dynamicznych prawie nigdy nie jest to możliwe przy realistycznych programach, mimo że rozsądna heurystyka przez większość czasu wydaje się słuszna. Kompilacja wymaga gwarancji czasu kompilacji . Mówiąc o tym, że „w wielu okolicznościach” wcale nie odnosisz się do mojej odpowiedzi.Szybki wskaźnik wskazujący najgorszy scenariusz dla języków dynamicznych:
W rezultacie (pełnego) Perla nigdy nie można skompilować statycznie.
Ogólnie, jak zawsze, zależy. Jestem pewien, że jeśli spróbujesz emulować funkcje dynamiczne w języku kompilowanym statycznie, dobrze przemyślani tłumacze lub (częściowo) skompilowane warianty mogą zbliżyć się lub podważyć wydajność języków kompilowanych statycznie.
Inną kwestią, o której należy pamiętać, jest to, że języki dynamiczne rozwiązują inny problem niż C. C jest zaledwie przyjemną składnią asemblera, podczas gdy języki dynamiczne oferują bogate abstrakcje. Wydajność środowiska wykonawczego często nie jest najważniejsza: na przykład czas wprowadzenia produktu na rynek zależy od tego, czy programiści będą w stanie napisać złożone systemy wysokiej jakości w krótkich ramach czasowych. Rozszerzalność bez ponownej kompilacji, na przykład za pomocą wtyczek, to kolejna popularna funkcja. Który język preferujesz w tych przypadkach?
źródło
Próbując udzielić bardziej obiektywnej odpowiedzi naukowej na to pytanie, argumentuję w następujący sposób. Język dynamiczny wymaga tłumacza lub środowiska wykonawczego do podejmowania decyzji w czasie wykonywania. Ten interpreter lub środowisko wykonawcze jest programem komputerowym i jako taki został napisany w pewnym języku programowania, statycznym lub dynamicznym.
Jeśli interpreter / środowisko wykonawcze zostało napisane w języku statycznym, wówczas można napisać program w tym języku statycznym, który (a) pełni tę samą funkcję co program dynamiczny, który interpretuje i (b) wykonuje co najmniej równie dobrze. Mamy nadzieję, że jest to oczywiste, ponieważ dostarczenie dokładnego dowodu tych roszczeń wymagałoby dodatkowego (prawdopodobnie znacznego) wysiłku.
Zakładając, że te twierdzenia są prawdziwe, jedynym wyjściem jest wymaganie, aby tłumacz / środowisko wykonawcze również było napisane w języku dynamicznym. Jednak natrafiamy na ten sam problem, co wcześniej: jeśli interpreter jest dynamiczny, wymaga interpretera / środowiska wykonawczego, który również musi być napisany w języku programowania, dynamicznym lub statycznym.
O ile nie założymy, że instancja tłumacza jest zdolna do interpretacji samego siebie w czasie wykonywania (mam nadzieję, że jest to oczywiście absurdalne), jedynym sposobem na pokonanie języków statycznych jest interpretacja każdej instancji tłumacza przez osobną instancję tłumacza; prowadzi to albo do nieskończonego regresu (mam nadzieję, że to jest oczywiste absurdalne), albo do zamkniętej pętli tłumaczy (mam nadzieję, że jest to również oczywiste absurdalne).
Wydaje się zatem, że nawet teoretycznie języki dynamiczne nie mogą działać lepiej niż języki statyczne. Podczas korzystania z modeli realistycznych komputerów wydaje się to jeszcze bardziej prawdopodobne; w końcu maszyna może wykonywać tylko sekwencje instrukcji maszyny, a wszystkie sekwencje instrukcji maszyny mogą być kompilowane statycznie.
W praktyce dopasowanie wydajności języka dynamicznego do języka statycznego może wymagać ponownego wdrożenia interpretera / środowiska wykonawczego w języku statycznym; jednak to, co możesz zrobić, to sedno i sedno tego argumentu. To pytanie z kurczaka i jaja i pod warunkiem, że zgadzasz się z niepotwierdzonymi (choć moim zdaniem głównie oczywistymi) założeniami podanymi powyżej, możemy faktycznie na nie odpowiedzieć; musimy skinąć głową statycznym, a nie dynamicznym językom.
Innym sposobem udzielenia odpowiedzi na pytanie, w świetle tej dyskusji, jest: w przechowywanym programie model sterowania = danych w obliczeniach, który leży u podstaw współczesnego przetwarzania, rozróżnienie między kompilacją statyczną a dynamiczną jest fałszywą dychotomią; statycznie skompilowane języki muszą mieć możliwość generowania i wykonywania dowolnego kodu w czasie wykonywania. Jest to zasadniczo związane z uniwersalnym obliczeniem.
źródło
main(args) { for ( i=0; i<1000000; i++ ) { if ( args[0] == "1" ) {...} else {...} }
Można znacznie przyspieszyć, gdyargs
znana jest wartość (zakładając, że nigdy się nie zmienia, co możemy potwierdzić). Kompilator statyczny nie może nigdy utworzyć kodu, który porzuca porównanie. (Oczywiście w tym przykładzie po prostu wyciągaszif
z pętli. Ale sprawa może być bardziej skomplikowana.)Myślę, że odpowiedź brzmi „tak” . Wierzę również, że mogą nawet przewyższyć obecną architekturę C / C ++ pod względem wydajności (nawet jeśli nieznacznie).
Powód jest prosty: w czasie wykonywania jest więcej informacji niż w czasie kompilacji.
Typy dynamiczne są tylko niewielką przeszkodą: jeśli funkcja jest zawsze lub prawie zawsze wykonywana z tymi samymi typami argumentów, optymalizator JIT może wygenerować gałąź i kod maszynowy dla tego konkretnego przypadku. I można zrobić o wiele więcej.
Zobacz Dynamic Languages Strike Back , przemówienie Steve'a Yegge z Google (gdzieś wierzę, że jest też wersja wideo). Wspomina o konkretnych technikach optymalizacji JIT z V8. Inspirujące!
Nie mogę się doczekać tego, co będziemy mieć w ciągu najbliższych 5 lat!
źródło
Ludzie, którzy najwyraźniej uważają, że jest to teoretycznie możliwe, lub w dalekiej przyszłości, całkowicie się mylę. Chodzi o to, że dynamiczne języki zapewniają i narzucają zupełnie inny styl programowania. W rzeczywistości różnica jest dwojaka, nawet jeśli oba aspekty są ze sobą powiązane:
Drugi punkt zapewnia ogólność za darmo. Zauważ, że tutaj struktury są elementami złożonymi, kolekcjami, ale także samymi typami, a nawet (!) Procedurami wszelkiego rodzaju (funkcje, akcje, operacje) ... Możemy pisać struktury według ich typów elementów, ale ze względu na pierwszy punkt i tak sprawdzenie nastąpi w czasie wykonywania. Moglibyśmy wpisywać symbole i nadal mieć uporządkowane te, które nie są wpisywane zgodnie z typami elementów (tablica
a
byłaby po prostu typowana jako tablica, a nie tablica liczb całkowitych), ale nawet te kilka nie jest prawdziwe w języku dynamicznym (a
może również zawierać ciąg).Element
Element
Jest dla mnie jasne, że to tylko ogromna kara za perf; i nawet nie dotykam wszystkich konsekwencji (niezliczonej liczby testów uruchomieniowych wszelkiego rodzaju niezbędnych do zapewnienia wrażliwości programu) dobrze opisanych w innych postach.
źródło
Nie miałem czasu na szczegółowe przeczytanie wszystkich odpowiedzi ... ale byłem rozbawiony.
Podobne kontrowersje pojawiły się w latach sześćdziesiątych i wczesnych siedemdziesiątych (historia informatyki często się powtarza): można skompilować języki wysokiego poziomu w celu wytworzenia kodu tak wydajnego jak kod maszynowy, cóż, powiedzmy kod asemblacyjny, wytworzony ręcznie przez programistę. Wszyscy wiedzą, że programista jest znacznie mądrzejszy niż jakikolwiek program i może zaproponować bardzo inteligentną optymalizację (myśląc właściwie o tym, co nazywa się teraz optymalizacją wizjera). To oczywiście ironia z mojej strony.
Pojawiła się nawet koncepcja rozszerzenia kodu: stosunek wielkości kodu wygenerowanego przez kompilator do wielkości kodu dla tego samego programu wyprodukowanego przez dobrego programistę (tak jakby było ich zbyt wiele :-). Oczywiście chodziło o to, aby stosunek ten był zawsze większy niż 1. Językami tamtych czasów byli Cobol i Fortran 4 lub Algol 60 dla intelektualistów. Myślę, że Lisp nie był brany pod uwagę.
Cóż, pojawiły się pogłoski, że ktoś opracował kompilator, który czasami może uzyskać współczynnik rozszerzenia 1 ... dopóki nie stała się regułą, że skompilowany kod jest znacznie lepszy niż kod pisany ręcznie (i również bardziej niezawodny). W tamtych czasach ludzie martwili się rozmiarem kodu (małe wspomnienia), ale to samo dotyczy szybkości lub zużycia energii. Nie będę przedstawiał powodów.
Dziwne cechy, dynamiczne cechy języka nie mają znaczenia. Liczy się sposób, w jaki są używane, czy są używane. Wydajność, niezależnie od jednostki (rozmiar kodu, prędkość, energia, ...) często zależy od bardzo małych części programów. Dlatego istnieje spora szansa, że urządzenia, które dają ekspresyjną moc, tak naprawdę nie przeszkadzają. Przy dobrej praktyce programowania zaawansowane urządzenia są wykorzystywane tylko w zdyscyplinowany sposób, aby wyobrazić sobie nowe struktury (to była lekcja seplenienia).
Fakt, że język nie ma pisania statycznego, nigdy nie oznacza, że programy napisane w tym języku nie są wpisywane statycznie. Z drugiej strony może się zdarzyć, że system typów używany przez program nie jest jeszcze wystarczająco sformalizowany, aby istniał teraz moduł sprawdzania typu.
W dyskusji pojawiło się kilka odniesień do analizy najgorszego przypadku („problem zatrzymania”, parsowanie PERL). Jednak analiza najgorszego przypadku jest w większości nieistotna. Liczy się to, co dzieje się w większości przypadków lub w użytecznych przypadkach ... jakkolwiek zdefiniowane, zrozumiane lub doświadczone. Oto kolejna historia, bezpośrednio związana z optymalizacją programu. Miało to miejsce dawno temu na dużym uniwersytecie w Teksasie, pomiędzy doktorantem i jego doradcą (który został później wybrany w jednej z akademii krajowych). O ile pamiętam, student nalegał na przestudiowanie problemu z analizą / optymalizacją, który doradca okazał się nieuleczalny. Wkrótce przestali mówić. Ale uczeń miał rację: problem był na tyle praktyczny, że w większości praktycznych przypadków rozprawa, którą stworzył, stała się pracą referencyjną.
Aby dodatkowo skomentować stwierdzenie, że
Perl parsing is not computable
cokolwiek rozumie się przez to zdanie, istnieje podobny problem z ML, który jest niezwykle dobrze sformalizowanym językiem.Type checking complexity in ML is a double exponential in the lenght of the program.
Jest to bardzo precyzyjny i formalny wynik w najgorszym przypadku złożoności ... co nie ma znaczenia. Afaik, użytkownicy ML nadal czekają na praktyczny program, który rozbije moduł sprawdzania typu.W wielu przypadkach, tak jak wcześniej, ludzki czas i kompetencje są rzadsze niż moc obliczeniowa.
Prawdziwym problemem w przyszłości będzie ewolucja naszych języków w celu zintegrowania nowej wiedzy, nowych form programowania, bez konieczności przepisywania całego nadal używanego oprogramowania.
Jeśli spojrzysz na matematykę, jest to bardzo duży zasób wiedzy. Języki używane do jej wyrażania, notacje i pojęcia ewoluowały na przestrzeni wieków. Łatwo jest pisać stare twierdzenia z nowymi koncepcjami. Dostosowujemy główne dowody, ale nie zawracamy sobie głowy wieloma wynikami.
Ale w przypadku programowania może być konieczne przepisanie wszystkich odbitek próbnych od zera (programy są próbkami). Może być tak, że tak naprawdę potrzebujemy bardzo wysokiego poziomu i rozwijalnych języków programowania. Projektanci optymalizatorów chętnie podążą za nimi.
źródło
Kilka uwag:
Nie wszystkie języki wysokiego poziomu są dynamiczne. Haskell ma bardzo wysoki poziom, ale jest w pełni statyczny. Nawet systemy programowania języków, takie jak Rust, Nim i D, potrafią wyrażać abstrakcje wysokiego poziomu zwięźle i skutecznie. W rzeczywistości mogą być tak zwięzłe jak języki dynamiczne.
Istnieją wysoce zoptymalizowane kompilatory z wyprzedzeniem dla języków dynamicznych. Dobre wdrożenia Lisp osiągają połowę prędkości odpowiadającej C.
Kompilacja JIT może być tutaj dużą wygraną. CloudFlare Web Application Firewall generuje kod Lua, który jest wykonywany przez LuaJIT. LuaJIT mocno optymalizuje faktycznie wykonane ścieżki wykonywania (zwykle ścieżki nieatakowe), w wyniku czego kod działa znacznie szybciej niż kod generowany przez statyczny kompilator przy rzeczywistym obciążeniu. W przeciwieństwie do statycznego kompilatora z optymalizacją sterowaną profilem, LuaJIT dostosowuje się do zmian ścieżek wykonywania w czasie wykonywania.
Decydująca jest także dezimtymizacja . Zamiast kodu skompilowanego w JIT, który musi sprawdzać, czy klasa jest ładowana małpowo, czynność małpowania powoduje zawieszenie w systemie wykonawczym, który odrzuca kod maszynowy zależny od starej definicji.
źródło