В настоящий момент я запускаю на выполнение 10000 параллельных задач посредством new Thread(...).Start().
Вначале они дерутся за процессорное время, но вскоре повисают на ожидании чего-либо — блокировок, ответа от сокета, просто в Thread.Sleep
Существуют ли какие-либо рекомендации по оптимизации подобных сценариев? Исходим из того, что реально одновременно выполняться может не больше 128 потоков, остальное время мы вынуждены гонять контекст между ними. Но из-за вышеупомянутых задержек, 10000 потоков с постоянным переключением контекста, работают быстрее, нежели 128, которые большую часть времени жизни спят.
LW>Вначале они дерутся за процессорное время, но вскоре повисают на ожидании чего-либо — блокировок, ответа от сокета, просто в Thread.Sleep
Писать неблокирующий код?
Re: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, LWhisper, Вы писали:
LW>В настоящий момент я запускаю на выполнение 10000 параллельных задач посредством new Thread(...).Start().
Как всегда, нулевое правило: если упёрся в стену — не набивать шишки, а искать момент, в который свернул не туда.
Попробуйте сами найти корень проблемы, вопрос для разогрева — "а зачем мне вообще понадобились 10k потоков с вычислениями?".
Re: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, LWhisper, Вы писали:
LW>Всем привет.
LW>В настоящий момент я запускаю на выполнение 10000 параллельных задач посредством new Thread(...).Start(). LW>Вначале они дерутся за процессорное время, но вскоре повисают на ожидании чего-либо — блокировок, ответа от сокета, просто в Thread.Sleep
LW>Существуют ли какие-либо рекомендации по оптимизации подобных сценариев? Исходим из того, что реально одновременно выполняться может не больше 128 потоков, остальное время мы вынуждены гонять контекст
между ними. Но из-за вышеупомянутых задержек, 10000 потоков с постоянным переключением контекста, работают быстрее, нежели 128, которые большую часть времени жизни спят.
Использовать пул потоков, который будет пережевывать поступающие задачи. Задача может представлять собой некий объект с состоянием и от него плясать — идти на выполнение или ждать события.
Здравствуйте, LWhisper, Вы писали:
LW>Существуют ли какие-либо рекомендации по оптимизации подобных сценариев? Исходим из того, что реально одновременно выполняться может не больше 128 потоков, остальное время мы вынуждены гонять контекст между ними. Но из-за вышеупомянутых задержек, 10000 потоков с постоянным переключением контекста, работают быстрее, нежели 128, которые большую часть времени жизни спят.
Во-первых есть ThreadPool, который по идее изначально подстраивается под систему. Обычно юзают его. Но все таки лучше вручную проверить.
Во-вторых нужно смотреть где стопорится. Если это дедлок -- то проблема разработчика. Еще есть такая штука как лимит подключений по умолчанию -- он равен 2 одновременным подключениям (емнип). Исправляется одной строчкой в конфиг-файле. Кроме того, если вы обращаетесь к одному серверу -- то у него могут быть свои лимиты на 1 IP-адрес -- исправляется запросами через PROXY-сервера.
Re[2]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, Shmj, Вы писали:
S>Во-вторых нужно смотреть где стопорится. Если это дедлок -- то проблема разработчика. Еще есть такая штука как лимит подключений по умолчанию -- он равен 2 одновременным подключениям (емнип). Исправляется одной строчкой в конфиг-файле. Кроме того, если вы обращаетесь к одному серверу -- то у него могут быть свои лимиты на 1 IP-адрес -- исправляется запросами через PROXY-сервера.
ThreadPool просто выжрет все доступные потоки и поставит остальные в очередь.
Нет проблемы с дедлоками. Есть проблема с тем, что крутится 10000 параллельно работающих потоков.
Re[2]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, DeathKnight, Вы писали:
DK>Использовать пул потоков, который будет пережевывать поступающие задачи. Задача может представлять собой некий объект с состоянием и от него плясать — идти на выполнение или ждать события.
DK>https://en.wikipedia.org/wiki/Work_stealing
Очевидное и очень хорошее решение. Проблема в том, что под него нужно перепроектировать всё приложение. Там где это можно было сделать без жертв, уже свернул 5000 потоков в 2.
Остались очень тяжеловесные места. Хочется обойтись минимальным количеством переделок.
Для того чтобы переписать все с поддержкой CancelableToken (ведь задача может зависнуть и тогда встанет вся очередь) понадобятся сотни человекочасов.
Re[2]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, LWhisper, Вы писали:
LW>>В настоящий момент я запускаю на выполнение 10000 параллельных задач посредством new Thread(...).Start().
S>Как всегда, нулевое правило: если упёрся в стену — не набивать шишки, а искать момент, в который свернул не туда. S>Попробуйте сами найти корень проблемы, вопрос для разогрева — "а зачем мне вообще понадобились 10k потоков с вычислениями?".
Корень проблемы давно известен. Ошибка допущена 4 года назад. Легко и просто она не исправляется.
Просто перейти на очередь задач сейчас не получится, так как задачи имеют обыкновение виснуть в неожиданный момент и тогда встрянет весь пул задач.
Поэтому сейчас ищется волшебная кнопка "сделать хорошо". То, что можно переписать без больших рисков, я переписываю. К сожалению, это капля в море. Просто скейлиться начала та часть продукта, о которой 4 года назад никто не думал и считал её если и многопоточной, то в пределах 3-4 параллельных задач, но никак не 10000. Классика жанра, но с этим нужно что-то делать.
Re[2]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, b0r3d0m, Вы писали:
LW>>Вначале они дерутся за процессорное время, но вскоре повисают на ожидании чего-либо — блокировок, ответа от сокета, просто в Thread.Sleep B>Писать неблокирующий код?
Поздно.
Re[3]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, LWhisper, Вы писали:
LW>Для того чтобы переписать все с поддержкой CancelableToken (ведь задача может зависнуть и тогда встанет вся очередь)
Встанет вся очередь только если все 10000 задач линейно зависят друг от друга. Но тогда вообще непонятно зачем тебе потоки.
Кроме того, таймаут в TPL никто не отменял.
Мне вот интересно другое — а сейчас ты чего с зависшей задачей делаешь? Abort зовешь?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, LWhisper, Вы писали:
LW>Корень проблемы давно известен. Ошибка допущена 4 года назад. Легко и просто она не исправляется.
А, ну это классика. Вам надо в любом случае перевести код на Task<T> и использовать cps (await) вместо блокирующих ожиданий, иначе вы так и будете замусоривать общий пул "зависшими" задачами.
Дальше проще. В принципе, можно даже попытать счастья с стандартным пулом, запуская проблемные места как LongRunning task. Не хватает — пишем свой scheduler c локальными очередями, общей очередью и отдельным подпулом для "долгих" задач. Прерывание не используете вообще, как я понимаю, поэтому по таймеру придётся переводить "зависшие" потоки из общего пула в пул для долгих задач (и наоборот).
Разумеется, все места, которые в явном виде зависят от Thread/TLS/используют блокировки придётся переписать. Если этого не сделать, то так и будете наступать себе на шнурки. Даже 100 активных потоков на одно ядро — это много.
Re[4]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, AndrewVK, Вы писали:
AVK>Встанет вся очередь только если все 10000 задач линейно зависят друг от друга. Но тогда вообще непонятно зачем тебе потоки. AVK>Кроме того, таймаут в TPL никто не отменял. AVK>Мне вот интересно другое — а сейчас ты чего с зависшей задачей делаешь? Abort зовешь?
Ты как-то противоречишь сам себе. Если мы переходим на очередь из которой разгребаются задачи, то в ней не может быть долгих таймаутов. В противном случае мы ждём у моря погоды вместо того чтобы выполнять другие задачи. Сейчас я могу себе позволить подождать сутки, а после срубить поток через Thread.Abort. С пулом такой фокус не прокатит. Придётся прошерстить весь код и любое действие, которое может подвиснуть отбрасывать в очередь отдельной таской с коллбэком. Фактически, переписать всё решение. Нереально.
Re[4]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, Sinix, Вы писали:
S>А, ну это классика. Вам надо в любом случае перевести код на Task<T> и использовать cps (await) вместо блокирующих ожиданий, иначе вы так и будете замусоривать общий пул "зависшими" задачами.
Классика, кто спорит.
С Task<T> всё очень грустно. Было вкручено в одном единственном месте. Выстрелило моментально — 200 задач в ожидании запуска. В Mono LongRunning-таски запускаются просто в отдельном Thread'е — получаем нашу ситуацию. В C# какая-то хитрая логика в зависимости от шедуллера. С дефолтным не прокатило.
До awiat'ов ещё не доросли. Поэтому и используем Thread'ы вместо Task'ок из пула. Но, как видно, имеем палку о двух концах.
S>Дальше проще. В принципе, можно даже попытать счастья с стандартным пулом, запуская проблемные места как LongRunning task. Не хватает — пишем свой scheduler c локальными очередями, общей очередью и отдельным подпулом для "долгих" задач. Прерывание не используете вообще, как я понимаю, поэтому по таймеру придётся переводить "зависшие" потоки из общего пула в пул для долгих задач (и наоборот).
Увы, со стандартным пулом всё тухло. Насчёт LRT выше.
Можно подробнее про свой шедуллер? Есть полезные статьи на примете?
S>Разумеется, все места, которые в явном виде зависят от Thread/TLS/используют блокировки придётся переписать. Если этого не сделать, то так и будете наступать себе на шнурки. Даже 100 активных потоков на одно ядро — это много.
Очень медленно и печальном этим занимаемся.
Re[2]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, LWhisper, Вы писали:
LW>>Существуют ли какие-либо рекомендации по оптимизации подобных сценариев? Исходим из того, что реально одновременно выполняться может не больше 128 потоков, остальное время мы вынуждены гонять контекст между ними. Но из-за вышеупомянутых задержек, 10000 потоков с постоянным переключением контекста, работают быстрее, нежели 128, которые большую часть времени жизни спят.
S>Во-первых есть ThreadPool, который по идее изначально подстраивается под систему. Обычно юзают его. Но все таки лучше вручную проверить.
А вот не надо так делать: ThreadPool могут использовать и другие библиотеки(хоть сам фреймворк) под свои нужды. И если все потоки у него отожрать, да еще и заблокировать их -- будет не очень здорово.
Кодом людям нужно помогать!
Re[5]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, LWhisper, Вы писали:
LW>Ты как-то противоречишь сам себе. Если мы переходим на очередь из которой разгребаются задачи, то в ней не может быть долгих таймаутов.
Очередь это слишком примитивная модель того, что происходит внутри TPL. Стандартный алгоритм и сам умеет в какой то мере бороться с долгоиграющим кодом, и возможностей по настройке там масса.
LW> В противном случае мы ждём у моря погоды вместо того чтобы выполнять другие задачи.
Тебе вроде уже несколько человек написали, что твоя теория не соответствует действительности.
LW> Сейчас я могу себе позволить подождать сутки, а после срубить поток через Thread.Abort.
Не можешь. Нельзя Thread.Abort использовать от слова вообще.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, LWhisper, Вы писали:
LW>В Mono LongRunning-таски запускаются просто в отдельном Thread'е
В дотнете тоже.
LW> — получаем нашу ситуацию.
Вашу это какую? Пул в современном дотнете совсем не фиксирован и может расти до довольно больших величин.
LW>В C# какая-то хитрая логика в зависимости от шедуллера.
В случае с LR нет там ничего особо хитрого. Исходники есть — можешь посмотреть, там немного.
И при чем здесь C#?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK>Очередь это слишком примитивная модель того, что происходит внутри TPL. Стандартный алгоритм и сам умеет в какой то мере бороться с долгоиграющим кодом, и возможностей по настройке там масса.
В какой-то мере умеет. Этого недостаточно. Примеры?
AVK>Тебе вроде уже несколько человек написали, что твоя теория не соответствует действительности.
Вроде, нет.
AVK>Не можешь. Нельзя Thread.Abort использовать от слова вообще.
Вполне себе могу. Код корректно обрабатывает ThreadAbortException и использует CER согласно правилам, там где это необходимо.
Re[6]: Как оптимизировать выполнения 10000 параллельных задач?
Здравствуйте, AndrewVK, Вы писали:
AVK>В дотнете тоже.
Да, верно. Итого 10000 LRT ничем не отличаются от 10000 тредов.
if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
{
Thread thread = new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork);
thread.IsBackground = true;
Task task1 = task;
thread.Start((object) task1);
}
AVK>Вашу это какую? Пул в современном дотнете совсем не фиксирован и может расти до довольно больших величин.
С 10000 потоками.
AVK>В случае с LR нет там ничего особо хитрого. Исходники есть — можешь посмотреть, там немного. AVK>И при чем здесь C#?
.NET имел ввиду. С LR нет, но, как мы уже выяснили, разницы между LRT и тредами нет.
Re[7]: Как оптимизировать выполнения 10000 параллельных задач?