Занадто низьке використання процесора багатопотокової програми Java для Windows


18

Я працюю над додатком Java для вирішення класу задач з числовою оптимізацією - більш точні проблеми лінійного програмування. Одну проблему можна розділити на менші підпрограми, які можна вирішити паралельно. Оскільки є більше підпроблем, ніж ядер CPU, я використовую 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 (Точка 8, 9, 10, 11).
  • різні рідні бібліотеки різних рішень лінійного програмування (CLP, Xpress, Cplex, Gurobi).

Запитання

  • Що визначає різницю продуктивності між Linux та Windows великого багатопотокового додатку Java, який широко використовує вбудовані дзвінки?
  • Чи можу я змінити впровадження, що могло б допомогти Windows, наприклад, чи слід уникати використання ExecutorService, який отримує тисячі викликів, і робити що замість цього?

Ви пробували ForkJoinPoolзамість ExecutorService? 25% використання процесора дуже низьке, якщо ваша проблема пов'язана з процесором.
Кароль Доубецький

1
Ваша проблема звучить як щось, що повинно підштовхнути CPU до 100%, і все ж ви на 25%. Для деяких проблем ForkJoinPoolце більш ефективно, ніж ручне планування.
Кароль Доубецький

2
Перекочуючи версії Hotspot, чи переконалися ви, що використовуєте "серверну", а не "клієнтську" версію? Яке використання вашого процесора в Linux? Крім того, тривалість роботи в Windows на кілька днів вражає! У чому ваш секрет? : P
erickson

3
Можливо, спробуйте використовувати Xperf для створення FlameGraph . Це може дати вам деяке уявлення про те, що робить процесор (сподіваємось, і користувач, і режим ядра), але я ніколи цього не робив у Windows.
Кароль Доубецький

1
@Nils, обидва запуски (unix / win) використовують один інтерфейс для виклику рідної бібліотеки? Запитую, бо це виглядає інакше. Як: win використовує jna, linux jni.
SR

Відповіді:


2

Для Windows кількість потоків на процес обмежена адресним простором процесу (див. Також Марк Русинович - Проштовхування меж Windows: Процеси та нитки ). Подумайте, що це викликає побічні ефекти, коли воно наближається до меж (уповільнення переключення контексту, фрагментація ...). Для Windows я б спробував розділити робоче навантаження на набір процесів. Для подібної проблеми, яку я мав років тому, я застосував бібліотеку Java, щоб зробити це зручніше (Java 8), погляньте, якщо вам подобається: Бібліотека для нерестування завдань у зовнішньому процесі .


Це виглядає дуже цікаво! Я трохи не вагаюся йти так далеко (поки що) з двох причин: 1) буде продуктивність накладних витрат на серіалізацію та відправлення об'єктів через сокети; 2) якщо я хочу серіалізувати все, це включає всі залежності, які пов'язані у завданні - було б небагато роботи над тим, щоб переписати код - тим не менше, дякую за корисні посилання.
Нілс

Я повністю поділяю ваші занепокоєння, і перероблення коду було б певних зусиль. Під час обходу графіка вам потрібно буде ввести поріг кількості потоків, коли настав час розділити роботу на новий підпроцес. Для адреси 2) подивіться на карту пам'яті Java-файлу (java.nio.MappedByteBuffer), за допомогою якої можна ефективно обмінюватися даними між процесами, наприклад, вашими графічними даними. Godspeed :)
гері

0

Здається, що Windows кешує деяку пам’ять у файлі сторінки, після того, як протягом деякого часу його не торкається, і ось чому процесор обмежується швидкістю диска

Ви можете перевірити це за допомогою Провідника процесів і перевірити, скільки пам’яті збережено в кеші


Ви думаєте? Вільної пам’яті достатньо. Чому Windows почала б обмінюватися? У будь-якому випадку, дякую.
Нілс

Принаймні, на моїх ноутбуках Windows іноді
єврей

0

Я думаю, що ця різниця в продуктивності пов'язана з тим, як ОС управляє потоками. JVM приховає всю різницю в ОС. Є багато сайтів, де ви можете прочитати про це, наприклад це , наприклад. Але це не означає, що різниця зникає.

Я думаю, ви працюєте на Java 8+ JVM. У зв'язку з цим фактом я пропоную вам спробувати використовувати функції потокового та функціонального програмування. Функціональне програмування дуже корисно, коли у вас є багато маленьких незалежних проблем і ви хочете легко перейти від послідовного до паралельного виконання. Хороша новина полягає в тому, що вам не потрібно визначати політику, щоб визначити, скільки потоків потрібно керувати (як, наприклад, з ExecutorService). Просто для прикладу (взято звідси ):

package com.mkyong.java8;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class ParallelExample4 {

    public static void main(String[] args) {

        long count = Stream.iterate(0, n -> n + 1)
                .limit(1_000_000)
                //.parallel()   with this 23s, without this 1m 10s
                .filter(ParallelExample4::isPrime)
                .peek(x -> System.out.format("%s\t", x))
                .count();

        System.out.println("\nTotal: " + count);

    }

    public static boolean isPrime(int number) {
        if (number <= 1) return false;
        return !IntStream.rangeClosed(2, number / 2).anyMatch(i -> number % i == 0);
    }

}

Результат:

Для звичайних потоків потрібно 1 хвилина 10 секунд. Для паралельних потоків потрібно 23 секунди. PS Тестується з i7-7700, 16G ОЗУ, WIndows 10

Отож, я пропоную вам прочитати про програмування функцій, потік, функцію лямбда на Java та спробувати впровадити невелику кількість тестів зі своїм кодом (адаптованим для роботи в новому контексті).


Я використовую потоки в інших частинах програмного забезпечення, але в цьому випадку завдання створюються під час обходу графіка. Я б не знав, як це обернути за допомогою потоків.
Нілс

Чи можете ви перейти до графіка, скласти список, а потім використовувати потоки?
xcesco

Паралельні потоки - це лише синтаксичний цукор для ForkJoinPool. Що я спробував (див. Коментар @KarolDowbecki вище).
Нілс

0

Будь ласка, опублікуйте статистику системи? Диспетчер завдань достатньо хороший, щоб надати певну підказку, якщо це єдиний доступний інструмент. Це може легко визначити, чи чекають ваші завдання на IO - це звучить як винуватець на основі того, що ви описали. Це може бути пов'язано з певним питанням управління пам'яттю, або бібліотека може записати на диск деякі тимчасові дані тощо.

Коли ви говорите про 25% використання процесора, ви маєте на увазі, що лише кілька ядер зайняті роботою одночасно? (Можливо, час від часу всі ядра працюють, але не одночасно.) Чи перевірили б ви, скільки потоків (або процесів) справді створено в системі? Чи число завжди більше, ніж кількість ядер?

Якщо ниток достатньо, чи багато з них в режимі очікування чогось чекають? Якщо це правда, ви можете спробувати перервати (або приєднати налагоджувач), щоб побачити, що їх чекає.


Я додав скріншот менеджера завдань для виконання, яке є репрезентативним для цієї проблеми. Сама програма створює стільки ниток, скільки на машині є фізичні ядра. Java вносить у цю фігуру трохи більше 50 ниток. Як уже було сказано в VisualVM, всі потоки зайняті (зеленими). Вони просто не підштовхують процесор до ліміту в Windows. Вони роблять на Linux.
Нілс

@Nils Я підозрюю, що ви насправді не займаєте всі теми одночасно , але насправді лише 9 - 10 з них. Вони заплановані випадковим чином на всіх ядрах, отже, ви в середньому використовуєте 9/44 = 20%. Ви можете використовувати потоки Java безпосередньо, а не ExecutorService, щоб побачити різницю? Створити 44 потоки не важко, і кожен захоплює Runnable / Callable з пулу завдань / черги. (Хоча VisualVM показує, що всі потоки Java зайняті, реальність може полягати в тому, що 44 потоки заплановано швидко, щоб усі вони отримали шанс запуститись у період вибірки VisualVM.)
Xiao-Feng Li

Це думка і те, що я насправді робив у якийсь момент. У своїй реалізації я також переконався, що нативний доступ є локальним для кожного потоку, але це взагалі не мало ніякої різниці.
Нілс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.