SwitchToThread против Sleep(1)
Мне интересно, какова реальная разница между вызовом Thread.Sleep(1) и вызовом SwitchToThread (если мы игнорируем, что он в настоящее время не выставлен BCL).
Джо Даффи упоминает в своем посте, что:
"В API ядра32! SwitchToThread нет проблем, которые делают Sleep (0) и Sleep (1)". (относительно поведения планировщика)
Почему Sleep не ведет себя так же, как SwitchToThread? Почему существует эта дифференциация и для чего она нужна? (если вообще..)
2 ответа
Есть два отличия. Первый упоминается в документах MSDN для SwitchToThread:
Производительность выполнения ограничена процессором вызывающего потока. Операционная система не будет переключать выполнение на другой процессор, даже если этот процессор простаивает или использует поток с более низким приоритетом.
Sleep(0) также позволит запускать потоки на других процессорах.
SwitchToThread также уступает только контексту планирования одного потока. Сон, с другой стороны, имеет множество условий, которых он ждет. Документы для SleepEx разъясняют это подробно:
* An I/O completion callback function is called
* An asynchronous procedure call (APC) is queued to the thread.
* The time-out interval elapses
Это приведет к нескольким потокам.
В общем, Sleep(0) с гораздо большей вероятностью даст временный интервал и ВСЕГДА уступит ОС, даже если другие потоки не ждут. Вот почему добавление Sleep(0) в цикле во многих случаях увеличит использование процессора со 100% (на ядро) до почти 0%. SwitchToThread не будет, если другой поток не ожидает квант времени.
SwitchToThread() - "умная" версия Sleep(0). Это не очень хорошо задокументировано, но в моем понимании это работает следующим образом:
- когда есть другие темы в
ready
состояние (т. е. имеется больше потоков, желающих работать, чем доступно логическим процессорам), и эти потоки имеют тот же или более высокий приоритет, чем поток, который вызывает SwitchToThread(), он ведет себя так же, как Sleep(0), т.е. логический процессор для одного из этих потоков с дорогой ценой переключения контекста; - когда есть темы в
ready
состояние с более низким приоритетом, он просто выходит, то есть поток, вызвавший SwitchToThread(), продолжает выполнение без каких-либо затрат на переключение контекста или переходы от 3 к кольцу 0 (он не выходит из режима пользователя) - это противоречит тому, как Sleep (0) ведет себя так, что всегда уступает управление даже потокам с самым низким приоритетом; - когда нет тем в
ready
состояние, SwitchToThread() также просто выходит из режима сна (0) - поэтому, если вы делаете это в цикле, вы просто получаете 100% -ную нагрузку на текущий логический процессор, то есть сжигаете питание.
Sleep(1) аналогичен Sleep(0), но с задержкой в 1 миллисекунду назад. Эта задержка в 1 миллисекунду освобождает логический процессор и не сжигает энергию. SwitchToThread, напротив, никогда не испытывает никаких задержек.
Поэтому лучше сравнивать SwitchToThread с Sleep(0), а не с Sleep(1), потому что Sleep(1) - это то же самое, что Sleep(0) + задержка в 1 миллисекунду.
Я позаимствовал некоторые идеи по этому вопросу из "Справочного руководства по оптимизации архитектур Intel 64 и IA-32" и "Руководства разработчика программного обеспечения для архитектур Intel 64 и IA-32", в которых рекомендуется называть некоторые pause
Инструкции процессора (также доступны как встроенные) через SwitchToThread() или Sleep(0), если ваше ожидание очень короткое. Обратите внимание, что SwitchToThread() или Sleep (0) являются почти мгновенными, в то время как Sleep(1) длится не менее одной миллисекунды.
Следующее также должно быть принято во внимание:
- Каждый вызов Sleep() или SwitchToThread() требует больших затрат на переключение контекста, которое может составлять более 10000 циклов.
- Это также сказывается на стоимости переходов от кольца 3 к кольцу 0, которые могут составлять 1000+ циклов.
- SwitchToThread() или Sleep (0) могут быть бесполезны, если в
ready
состояние, но Sleep(1) ожидает по крайней мере одну миллисекунду независимо от того, есть ли другие потоки в состоянии готовности или нет.
Если ваш цикл ожидания имеет тенденцию быть очень коротким, пожалуйста, подумайте о выполнении некоторых pause
Сначала инструкции процессора. Замедляя "вращение-ожидание" с некоторыми pause
Инструкции ЦП перед вызовом SwitchToThread() или Sleep(), многопоточное программное обеспечение получает:
- Производительность за счет упрощения задач ожидания для более легкого получения ресурсов из занятого ожидания.
- Экономия энергии благодаря использованию меньшего количества частей трубопровода во время вращения.
- Устранение подавляющего большинства ненужно выполняемых инструкций, вызванных накладными расходами вызова SwitchToThread() или Sleep (0) или Sleep(1).
Однако, если вы собираетесь вызывать Sleep(1), который выполняется по крайней мере на одну миллисекунду, что очень долго с точки зрения циклов ЦП, тогда вы ожидаете, что ваш цикл ожидания будет очень длинным, поэтому pause
инструкции будут бесполезны в этом случае.
Когда ожидается, что цикл ожидания будет длиться долго, предпочтительно уступить операционную систему, вызвав одну из функций API синхронизации ОС, например WaitForSingleObject в ОС Windows, но не SwitchToThread() или Sleep (0) или Sleep(1), так как они очень расточительны при долгом ожидании. Более того, Sleep(1) работает очень медленно, а функции синхронизации ОС, такие как WaitForSingleObject или EnterCriticalSection, будут реагировать намного быстрее и более дружественны к ресурсам.
Мой вывод: лучше не использовать Sleep (0) или Sleep(1) или SwitchToThread(). Избегайте петель "вращение-ожидание" любой ценой. Используйте высокоуровневые функции синхронизации, такие как WaitForMultipleObjects (), SetEvent () и т. Д. - они являются лучшими с точки зрения производительности, эффективности и энергосбережения. Хотя они также страдают от дорогостоящих переключений контекста и переходов от кольца 3 к кольцу 0, эти расходы нечасты и более чем разумны по сравнению с тем, что вы потратили бы в циклах "spin-wait" с Sleep() или SwitchToThread().
На процессоре, поддерживающем технологию HT, циклы ожидания с вращением могут занимать значительную часть пропускной способности процессора. Один логический процессор, выполняющий цикл ожидания вращения, может серьезно повлиять на производительность другого логического процессора. Вот почему иногда отключение HT может улучшить производительность.
Последовательный опрос устройств, файлов или других источников данных на предмет изменений состояния может привести к тому, что компьютер будет потреблять больше энергии, создавать нагрузку на память и системную шину и обеспечивать ненужные сбои страниц (используйте диспетчер задач в Windows, чтобы узнать, какие приложения генерируют большинство сбоев страниц в режиме ожидания - это наиболее неэффективные приложения, так как они используют "опрос"). Минимизируйте опрос, когда это возможно, и используйте управляемый событиями способ написания приложений. Это лучшая практика, которую я очень рекомендую. Ваше приложение должно буквально спать все время, ожидая нескольких событий, настроенных заранее. Хорошим примером приложения, управляемого событиями, является Nginx под Linux. Возьмите пример с опросом на предмет изменения источника питания. Если операционная система предоставляет услуги уведомления (даже сообщения WM_) для различных изменений состояния устройства, таких как переход источника питания от сети переменного тока к батарее, используйте эти службы уведомления вместо опроса об изменении состояния устройства. Такой подход уменьшает накладные расходы на код для опроса состояния источника питания, потому что код может получать уведомления асинхронно, когда происходят изменения состояния.
Вопреки тому, что некоторые люди писали, Sleep (0) не снижает потребление ресурсов процессора почти до нуля. Он освобождает выполнение другим потокам, которые находятся в состоянии "готовности", но если таких потоков нет, он просто тратит тысячи циклов ЦП и потребляет 100% циклов ЦП текущих потоков, что также было продемонстрировано членами stackru - и Я также только что проверил это снова - цикл Sleep (0) потребляет 100% ЦП текущего потока в Windows 10 64-битной.