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
jest bardziej wydajny niż planowanie ręczne.
ForkJoinPool
zamiastExecutorService
? 25% wykorzystania procesora jest naprawdę niskie, jeśli twój problem jest związany z procesorem.