Pracuję nad aplikacją Java do rozwiązywania problemów z optymalizacją numeryczną - a dokładniej - problemów programowania liniowego na dużą skalę. Pojedynczy problem można podzielić na mniejsze podproblemy, które można rozwiązać równolegle. Ponieważ jest więcej podproblemów niż rdzeni procesora, używam ExecutorService i definiuję każdy podproblem jako wywoływalny, który jest przesyłany do ExecutorService. Rozwiązanie podproblemu wymaga wywołania biblioteki natywnej - w tym przypadku liniowego solvera programistycznego.
Problem
Mogę uruchomić aplikację w systemach Unix i Windows z maksymalnie 44 rdzeniami fizycznymi i pamięcią do 256 g, ale czasy obliczeń w systemie Windows są o rząd wielkości wyższe niż w przypadku Linuksa w przypadku dużych problemów. Windows wymaga nie tylko znacznie więcej pamięci, ale wykorzystanie procesora z czasem spada z 25% na początku do 5% po kilku godzinach. Oto zrzut ekranu menedżera zadań w systemie Windows:
Spostrzeżenia
- Czasy rozwiązania dla dużych instancji ogólnego problemu wahają się od godzin do dni i zużywają do 32 g pamięci (w systemie Unix). Czasy rozwiązania dla podproblemu mieszczą się w zakresie ms.
- Nie spotykam tego problemu w przypadku drobnych problemów, których rozwiązanie zajmuje tylko kilka minut.
- Linux używa obu gniazd od razu po wyjęciu z pudełka, podczas gdy Windows wymaga ode mnie jawnej aktywacji przeplatania pamięci w systemie BIOS, aby aplikacja korzystała z obu rdzeni. Niezależnie od tego, czy to zrobię, nie ma to wpływu na pogorszenie ogólnego wykorzystania procesora w miarę upływu czasu.
- Kiedy patrzę na wątki w VisualVM, wszystkie wątki z puli są uruchomione, żaden nie czeka.
- Według VisualVM 90% czasu procesora jest przeznaczane na natywne wywołanie funkcji (rozwiązanie małego programu liniowego)
- Odśmiecanie nie stanowi problemu, ponieważ aplikacja nie tworzy i nie odwołuje wielu obiektów. Ponadto wydaje się, że większość pamięci jest przydzielana poza stertą. W przypadku największej instancji wystarcza 4 g sterty w systemie Linux i 8 g w systemie Windows.
Co próbowałem
- wszelkiego rodzaju argumenty JVM, wysokie XMS, wysokie metaspace, flaga UseNUMA, inne GC.
- różne maszyny JVM (Hotspot 8, 9, 10, 11).
- różne natywne biblioteki różnych liniowych solverów programistycznych (CLP, Xpress, Cplex, Gurobi).
pytania
- Co wpływa na różnicę wydajności między Linuksem a Windowsem w wielowątkowej aplikacji Java, która intensywnie wykorzystuje natywne połączenia?
- Czy jest coś, co mogę zmienić w implementacji, co pomogłoby systemowi Windows, na przykład, czy powinienem unikać korzystania z usługi ExecutorService, która odbiera tysiące wywołań, i zamiast tego robić?
ForkJoinPool
zamiastExecutorService
? 25% wykorzystania procesora jest naprawdę niskie, jeśli twój problem jest związany z procesorem.ForkJoinPool
jest bardziej wydajny niż planowanie ręczne.Odpowiedzi:
W systemie Windows liczba wątków na proces jest ograniczona przestrzenią adresową procesu (patrz także Mark Russinovich - Przesuwanie granic systemu Windows: procesy i wątki ). Pomyśl, że powoduje to skutki uboczne, gdy zbliża się do granic (spowolnienie przełączania kontekstu, fragmentacja ...). W systemie Windows starałbym się podzielić obciążenie pracą na zestaw procesów. W przypadku podobnego problemu, który miałem lata temu, zaimplementowałem bibliotekę Java, aby zrobić to wygodniej (Java 8), spójrz, jeśli chcesz: Biblioteka do odradzania zadań w procesie zewnętrznym .
źródło
Wygląda na to, że Windows buforuje trochę pamięci do pliku stronicowania, po tym jak był przez jakiś czas nietknięty, i dlatego szybkość dysku jest wąska
Możesz to sprawdzić za pomocą Eksploratora procesów i sprawdzić, ile pamięci jest buforowane
źródło
Myślę, że ta różnica wydajności wynika z tego, jak system operacyjny zarządza wątkami. JVM ukrywa wszystkie różnice w systemie operacyjnym. Istnieje wiele miejsc, gdzie można przeczytać o tym, jak ten , na przykład. Ale to nie znaczy, że różnica znika.
Podejrzewam, że korzystasz z JVM Java 8+. Z tego powodu proponuję, abyś spróbował użyć funkcji programowania strumieniowego i funkcjonalnego. Programowanie funkcjonalne jest bardzo przydatne, gdy masz wiele małych niezależnych problemów i chcesz łatwo przełączyć się z wykonywania sekwencyjnego na równoległe. Dobrą wiadomością jest to, że nie musisz definiować polityki, aby określić, ile wątków musisz zarządzać (jak w przypadku usługi ExecutorService). Na przykład (wzięte stąd ):
Proponuję więc przeczytać o programowaniu funkcji, streamie, funkcji lambda w Javie i spróbować zaimplementować niewielką liczbę testów za pomocą kodu (przystosowanych do pracy w tym nowym kontekście).
źródło
Czy mógłbyś opublikować statystyki systemu? Menedżer zadań jest wystarczająco dobry, aby dostarczyć wskazówek, jeśli jest to jedyne dostępne narzędzie. Może łatwo stwierdzić, czy twoje zadania czekają na IO - co brzmi jak winowajca na podstawie tego, co opisałeś. Może to wynikać z pewnych problemów z zarządzaniem pamięcią lub biblioteka może zapisać tymczasowe dane na dysku itp.
Kiedy mówisz o 25% wykorzystania procesora, czy masz na myśli, że tylko kilka rdzeni jest zajętych jednocześnie? (Może się zdarzyć, że wszystkie rdzenie działają od czasu do czasu, ale nie jednocześnie.) Czy sprawdziłbyś, ile wątków (lub procesów) jest naprawdę tworzonych w systemie? Czy liczba jest zawsze większa niż liczba rdzeni?
Jeśli jest wystarczająco dużo wątków, czy wiele z nich jest bezczynnych i czeka na coś? Jeśli to prawda, możesz spróbować przerwać (lub dołączyć debugger), aby zobaczyć, na co czekają.
źródło