Я работаю над Java-приложением для решения класса задач численной оптимизации, а точнее крупномасштабных задач линейного программирования. Одну проблему можно разбить на более мелкие подзадачи, которые можно решать параллельно. Поскольку подзадач больше, чем ядер ЦП, я использую ExecutorService и определяю каждую подзадачу как Callable, которая передается в ExecutorService. Решение подзадачи требует вызова собственной библиотеки — в данном случае решателя линейного программирования.
Проблема
Я могу запускать приложение в системах Unix и Windows, имеющих до 44 физических ядер и до 256 ГБ памяти, но время вычислений в Windows на порядок выше, чем в Linux для больших задач. Windows не только требует значительно больше памяти, но и загрузка ЦП со временем падает с 25% в начале до 5% через несколько часов. Вот скриншот диспетчера задач в Windows:
Наблюдения
- Время решения больших экземпляров общей проблемы варьируется от часов до дней и требует до 32 г памяти (в Unix). Время решения подзадачи находится в диапазоне мс.
- Я не сталкиваюсь с этой проблемой при небольших проблемах, решение которых занимает всего несколько минут.
- Linux использует оба сокета «из коробки», тогда как Windows требует, чтобы я явно активировал чередование памяти в BIOS, чтобы приложение использовало оба ядра. Независимо от того, делаю ли я это, это не влияет на ухудшение общей загрузки ЦП с течением времени.
- Когда я смотрю на потоки в VisualVM, все потоки пула работают, ни один из них не находится в ожидании или что-то еще.
- Согласно VisualVM, 90% процессорного времени тратится на вызов встроенной функции (решение небольшой линейной программы).
- Сборка мусора не является проблемой, поскольку приложение не создает и не разыменовывает большое количество объектов. Кроме того, кажется, что большая часть памяти выделена вне кучи. 4 г кучи достаточно в Linux и 8 г в Windows для самого большого экземпляра.
Что я пробовал
- всевозможные аргументы JVM, высокий XMS, высокий метапространство, флаг UseNUMA, другие GC.
- разные JVM (Hotspot 8, 9, 10, 11).
- различные нативные библиотеки различных решателей линейного программирования (CLP, Xpress, Cplex, Gurobi).
Вопросы
- Что определяет разницу в производительности между Linux и Windows для большого многопоточного Java-приложения, интенсивно использующего собственные вызовы?
- Есть ли что-то, что я могу изменить в реализации, что помогло бы Windows, например, следует ли мне избегать использования ExecutorService, который получает тысячи Callables, и делать что вместо этого?
ForkJoinPool
вместоExecutorService
? 25% загрузки ЦП действительно мало, если ваша проблема связана с ЦП. - person Karol Dowbecki   schedule 14.11.2019ForkJoinPool
более эффективен, чем планирование вручную. - person Karol Dowbecki   schedule 14.11.2019ForkJoinPool
не приносит облегчения. - person Nils   schedule 16.11.2019malloc
,realloc
). Когда ваш код решателя используетmalloc
,realloc
и т. д., то в длительном процессе тип распределителя может иметь значение. Это также может объяснить различия в различных операционных системах из-за фрагментации памяти и частоты попаданий в кэш. См. en.wikipedia.org/wiki/C_dynamic_memory_allocation. - person aventurin   schedule 17.11.2019CLPNative.javaclpInitialSolve(Pointer<CLPSimplex>)
- person Nils   schedule 21.11.2019BlockingQueue
; 4. Я использую callable только для перехвата исключений при сбое подзадачи и не отменяю их, но runnables ведут себя так же; 5. Нет. Спасибо :) - person Nils   schedule 23.11.2019