Я работаю над приложением 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% загрузка ЦП действительно низкая, если ваша проблема связана с ЦП.ForkJoinPool
это более эффективно, чем ручное планирование.Ответы:
Для Windows количество потоков на процесс ограничено адресным пространством процесса (см. Также Марк Руссинович - Расширение границ Windows: процессы и потоки ). Думаю, это вызывает побочные эффекты, когда приближается к пределам (замедление переключения контекста, фрагментация ...). Для Windows я бы попытался разделить рабочую нагрузку на набор процессов. Для аналогичной проблемы, с которой я столкнулся много лет назад, я реализовал библиотеку Java, чтобы сделать это более удобным (Java 8), если хотите, посмотрите: Библиотека для порождения задач во внешнем процессе .
источник
Похоже, что Windows кеширует некоторую память в файл подкачки, после того как некоторое время ее не трогали, и поэтому скорость процессора ограничена скоростью диска
Вы можете проверить это с помощью Process Explorer и проверить, сколько памяти кэшируется.
источник
Я думаю, что эта разница в производительности связана с тем, как ОС управляет потоками. JVM скрывает все различия ОС. Есть много сайтов , где вы можете прочитать о нем, как это , например. Но это не значит, что разница исчезает.
Я полагаю, вы работаете на Java 8+ JVM. В связи с этим я предлагаю вам попробовать использовать функции потокового и функционального программирования. Функциональное программирование очень полезно, когда у вас много мелких независимых проблем, и вы хотите легко переключаться с последовательного на параллельное выполнение. Хорошей новостью является то, что вам не нужно определять политику, чтобы определить, сколько потоков вы должны управлять (например, с помощью ExecutorService). Просто для примера (взято отсюда ):
Итак, я предлагаю вам прочитать о программировании функций, потоке, лямбда-функции в Java и попытаться реализовать небольшое количество тестов с вашим кодом (адаптированным для работы в этом новом контексте).
источник
Не могли бы вы опубликовать статистику системы? Диспетчер задач достаточно хорош, чтобы дать некоторую подсказку, если это единственный доступный инструмент. Он может легко определить, ожидают ли ваши задачи ввода-вывода - это звучит как преступник, основываясь на том, что вы описали. Это может быть связано с определенной проблемой управления памятью, или библиотека может записать некоторые временные данные на диск и т. Д.
Когда вы говорите, что загрузка процессора составляет 25%, вы имеете в виду, что одновременно работают только несколько ядер? (Может случиться так, что все ядра работают время от времени, но не одновременно.) Вы бы проверили, сколько потоков (или процессов) действительно создано в системе? Всегда ли число больше количества ядер?
Если потоков достаточно, многие из них простаивают, ожидая чего-то? Если это правда, вы можете попытаться прервать (или подключить отладчик), чтобы увидеть, что они ждут.
источник