Wiadomo, że czyste funkcje ułatwiają parellelizację. Co takiego jest w programowaniu funkcjonalnym, które sprawia, że jest ono z natury przystosowane do wykonywania równoległego?
Czy kompilatory takie jak Javac są wystarczająco inteligentne, aby wykryć, kiedy metoda jest czystą funkcją? Zawsze można zaimplementować klasy, które implementują funkcjonalne interfejsy, takie jak Function , ale mają skutki uboczne.
functional-programming
Naveen
źródło
źródło
NullPointerException
s. Korzyści wynikające z optymalizacji opartych na tym są również prawdopodobnie niewielkie w przypadku typowych aplikacji Java.Odpowiedzi:
To nie jest kwestia „wystarczająco inteligentna”. Nazywa się to analizą czystości i jest niemożliwe do udowodnienia w ogólnym przypadku: jest to równoważne z rozwiązaniem problemu zatrzymania.
Teraz, oczywiście, optymalizatory robią rzeczy niemożliwe do udowodnienia przez cały czas, „niemożliwe do udowodnienia w ogólnym przypadku” nie oznacza, że nigdy nie działa, to tylko oznacza, że nie może działać we wszystkich przypadkach. Istnieją więc algorytmy do sprawdzania, czy funkcja jest czysta, czy nie, po prostu częściej wynikiem jest „Nie wiem”, co oznacza, że ze względów bezpieczeństwa i poprawności należy założyć że ta konkretna funkcja może być nieczysta.
I nawet w przypadkach, gdy to robi pracę, algorytmy są skomplikowane i kosztowne.
To jest problem nr 1: działa tylko w szczególnych przypadkach .
Problem nr 2: Biblioteki . Aby funkcja była czysta, może ona zawsze wywoływać tylko funkcje czyste (i te funkcje mogą wywoływać tylko funkcje czyste itd.). Javac oczywiście wie tylko o Javie i wie tylko o kodzie, który widzi. Tak więc, jeśli twoja funkcja wywołuje funkcję w innej jednostce kompilacyjnej, nie możesz wiedzieć, czy jest czysta, czy nie. Jeśli wywołuje funkcję napisaną w innym języku, nie możesz tego wiedzieć. Jeśli wywołuje funkcję w bibliotece, która może nawet nie zostać jeszcze zainstalowana, nie możesz tego wiedzieć. I tak dalej.
Działa to tylko wtedy, gdy masz analizę całego programu, gdy cały program jest napisany w tym samym języku i wszystko jest kompilowane za jednym razem. Nie możesz używać żadnych bibliotek.
Problem nr 3: Planowanie . Po ustaleniu, które części są czyste, nadal musisz zaplanować je dla oddzielnych wątków. Albo nie. Uruchamianie i zatrzymywanie wątków jest bardzo kosztowne (szczególnie w Javie). Nawet jeśli utrzymujesz pulę wątków i nie uruchamiasz ich ani nie zatrzymujesz, przełączanie kontekstu wątków jest również drogie. Musisz mieć pewność, że obliczenia będą działały znacznie dłużej niż czas potrzebny na zaplanowanie i zmianę kontekstu, w przeciwnym razie stracisz wydajność, a nie ją zyskasz.
Jak zapewne już się domyślacie, ustalenie, jak długo potrwa obliczenie, jest ogólnie niemożliwe (nie możemy nawet ustalić, czy zajmie to skończoną ilość czasu, nie mówiąc już o tym, ile czasu), a także trudne i kosztowne, nawet w specjalny przypadek.
Na bok: Javac i optymalizacje . Zauważ, że większość implementacji javac w rzeczywistości nie wykonuje wielu optymalizacji. całkowicie go zoptymalizuj, z wyjątkiem tego, że nie może wykonywać wstawiania ponad granicami wątków, a zatem funkcja, którą można całkowicie zoptymalizować, jest teraz niepotrzebnie wykonywana.Na przykład implementacja javac przez Oracle polega na bazowym silniku wykonawczym w celu optymalizacji . Prowadzi to do kolejnego zestawu problemów: powiedzmy, javac zdecydował, że określona funkcja jest czysta i wystarczająco droga, więc kompiluje ją do wykonania na innym wątku. Następnie pojawia się optymalizator platformy (na przykład kompilator HotSpot C2 JIT) i optymalizuje całą funkcję. Teraz masz pusty wątek, który nic nie robi. Albo wyobraź sobie, że javac postanawia zaplanować funkcję w innym wątku, a optymalizator platformy mógłby to zrobić
Tak więc robienie czegoś takiego ma sens tylko wtedy, gdy masz jeden kompilator, który wykonuje większość optymalizacji za jednym razem, aby kompilator wiedział i mógł wykorzystać wszystkie różne optymalizacje na różnych poziomach i ich interakcje między sobą.
Należy zauważyć, że, na przykład, kompilator JIT HotSpot C2 rzeczywiście ma wykonać pewne auto wektoryzacji, który jest również formą auto-zrównoleglenia.
źródło
definition
, stosując odmiennedefinition
odpurity
jest prawdopodobnie niejasneStringBuilder
) nie ma sensu, więc odrzuciłbym to i po prostu zakładam, że OP pisze javac, ale oznacza Hotspot. Twój problem nr 2 jest całkiem dobrym powodem, aby nie optymalizować niczego w javac.W głosowanej odpowiedzi nie odnotowano jednej rzeczy. Synchroniczna komunikacja między wątkami jest niezwykle droga. Jeśli funkcja może być wykonywana z prędkością wielu milionów wywołań na sekundę, w rzeczywistości bardziej cię boli, aby ją zrównoleglić, niż pozostawić ją taką, jaka jest.
Najszybsza forma synchronicznej komunikacji między wątkami, wykorzystująca zajęte pętle ze zmiennymi atomowymi, jest niestety nieefektywna energetycznie. Jeśli musisz zastosować zmienne warunkowe w celu oszczędzania energii, wydajność komunikacji między wątkami ucierpi.
Tak więc kompilator nie tylko musi ustalić, czy funkcja jest czysta, ale musiałby także oszacować czas wykonania funkcji, aby zobaczyć, czy równoległość jest wygraną netto. Musiałby także wybierać między zajętymi pętlami za pomocą zmiennych atomowych lub zmiennych warunkowych. I musiałby tworzyć nici za twoimi plecami.
Dynamiczne tworzenie wątków jest nawet wolniejsze niż stosowanie zmiennych warunkowych. Kompilator musiałby więc skonfigurować pewną liczbę już uruchomionych wątków.
Odpowiedź na twoje pytanie brzmi: nie , kompilatory nie są wystarczająco „inteligentne”, aby automatycznie zrównoleglać czyste funkcje, szczególnie w świecie Java. Są sprytni, nie automatycznie je równolegle!
źródło