#c# #.net #multithreading
#c# #.net #многопоточность
Вопрос:
Я пишу критичный для производительности.СЕТЕВОЕ приложение, которое интенсивно использует многопоточность.
При использовании профилировщика производительности Visual Studio лучшими функциями с эксклюзивными примерами являются:
WaitHandle.WaitAny()
— 14.23%
@JIT_MonReliableEnter@8
— 7.76%
Monitor.Enter
— 5.09%
В принципе, мои лучшие 3 функции работают с потоковыми примитивами и, как я полагаю, в некоторой степени находятся вне моего контроля. Мои рабочие процедуры по сравнению с ними довольно малы, и я пытаюсь повысить производительность. Я считаю, что задействованные алгоритмы довольно надежны, хотя я довольно часто пересматриваю их.
Мои вопросы:
- Если в этих методах используется 14,23% выборок ЦП — эффективно ли процессор «простаивает» для большинства этих выборок, т. Е. просто ожидает других потоков? Или незанятая часть ожидания потока не отображается как часть трассировки профиля [а 27,08%, показанные в этих 3, представляют собой сумму всех накладных расходов в этих методах синхронизации]? (Я могу догадаться, что это в основном простаивает, но был бы признателен за какой-нибудь приличный справочный материал за ответами на этот, пожалуйста)
- Я пересмотрел свои схемы блокировки, однако указывают ли эти результаты на какое-то конкретное узкое место или метод, который я должен изучить для дальнейшей оптимизации?
WaitAny
В частности, довольно плохое? Я активно использую его для проверки того, доступны ли для чтения / записи определенные объекты очереди, но также одновременно проверяю флаг прерывания. Есть ли лучший способ сделать это?
Комментарии:
1. Это может быть способ, которым вы используете эти примитивы.
2. @Henk — действительно, я пытался намекнуть на это, сказав в обзоре моих алгоритмов схемы блокировки
3. @KierenJohnstone Если у вас версия VS 2010, вероятно, для этого лучше использовать профилировщик / визуализатор параллелизма.
4. Я использовал профилировщик / визуализатор параллелизма, текстовые результаты / данные показали мне утверждения и номера дескрипторов .. которые я не счел полезными, или график выполнения потока .. что тоже не показалось полезным. Возможно, мне нужно подумать о каком-то способе его использования, но даже в этом трудоемком многопоточном приложении это кажется броской функцией, которая не дает особого понимания..
Ответ №1:
Ваш процессор не обязательно простаивает, когда поток находится в WaitHandle.WaitAny
или a Monitor.Enter
. Поток, находящийся в режиме ожидания, простаивает, но предположительно другие потоки заняты выполнением. Это особенно верно для Monitor.Enter
. Если поток заблокирован из-за блокировки, то можно было бы надеяться, что поток, у которого есть эта блокировка, выполняет код, а не простаивает.
Кроме того, если ваш поток использует WaitAny
для чтения из очереди, то, скорее всего, в очереди просто ничего нет. Это не проблема производительности для потребительского кода. Это просто означает, что производитель недостаточно быстро ставит все в очередь. Это может быть связано с тем, что производитель работает медленно или данные поступают недостаточно быстро.
Если вы обрабатываете данные быстрее, чем они могут поступить, то не похоже, что у вас проблемы с производительностью. Конечно, не на стороне потребителя.
Что касается использования WaitAny
для постановки в очередь, я бы посоветовал вам использовать BlockingCollection и методы, которые принимают токен отмены, такие как TryAdd(T, Int32, CancellationToken). Преобразование в токены отмены действительно упростило мой многопоточный код очереди.
Комментарии:
1. Спасибо, я обязательно проверю эту коллекцию. С точки зрения общего использования ЦП, я знаю, что когда поток ожидает, другие могут быть заняты, есть идеи, хотя, все ли отснятые там образцы считаются потоком, в котором они были захвачены в режиме ожидания, или записанные образцы являются накладными расходами .net, а не временем, потраченным на «ожидание»?
2. Кроме того, я занимаюсь производством, а также потреблением. Производственный код был включен в эту трассировку профиля. Тогда я ожидал бы увидеть производственный код вверху — ???
3. @Kieren: Если в примере показано, что поток простаивает (в
WaitAny
илиMonitor.Enter
), то я бы ожидал, что он действительно простаивает. Можно было бы ожидать, что любые накладные расходы на профилирование будут удалены из статистики. Что касается того, что вы ожидаете увидеть, я недостаточно знаю о вашем приложении, чтобы сказать. Производитель также может находиться в режиме ожидания, если он ожидает данных из сетевого потока или файла на диске или чего-то еще.4. спасибо, эта часть имеет решающее значение — по вашему мнению, WaitAny и Monitor. Вводимые выборки представляют собой циклы простоя, но, согласно Igor, статистика профилирования не включает циклы простоя, и поэтому выборка является служебной в этих методах (примечание: не служебной для профилирования, а внутренней . ЧИСТЫЙ многопоточный код, который, например, переводит его в состояние ожидания). У вас есть источник / ссылка для этой части?
5. (Кстати, ваша ссылка на BlockingCollection ссылается на класс Task ;))
Ответ №2:
Статистика профилирования не включает время, когда потоки были заблокированы.
Профилировщик на основе выборки в основном запрашивает у каждого ядра отчет после каждых X (скажем, 1 000 000) циклов без простоя. Каждый раз, когда ядро отчитывается, профилировщик запоминает текущий стек вызовов. Результаты профилирования восстанавливаются на основе стеков вызовов, записанных профилировщиком.
Из результатов профилирования вы знаете, что 14,23% времени, когда ядро выполняло работу, оно выполняло инструкции в WaitHandle.Подождите немного. Если ваша программа привязана к процессору, оптимизация части ожидания (например, с использованием другого примитива) может оказать значительное влияние на производительность. Однако, если программа не привязана к процессору и проводит большую часть своего времени в ожидании на сервере, диске, другом процессе или каком-либо другом внешнем входе, то оптимизация кода, связанного с WaitAny, будет не очень полезной.
Итак, вашим следующим шагом должно быть выяснение того, какова загрузка процессора вашей программой. Также обратите внимание на визуализатор параллелизма, о котором упоминал Илиан, который может быть полезен для понимания того, как потоки в вашей программе проводят свое время.
Комментарии:
1. Программа в целом использует ~ 70% процессора, я рассмотрю множество альтернатив, таких как блокирование сбора, о котором говорит Джим.
2. Привет, Кирен, есть какие-нибудь обновления по этому поводу? как вы в конечном итоге улучшили потребление ЦП?