Я работаю над приложением 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
это более эффективно, чем ручное планирование.
ForkJoinPool
вместоExecutorService
? 25% загрузка ЦП действительно низкая, если ваша проблема связана с ЦП.