Comments 12
Выглядит очень похоже на OMP_WAIT_POLICY (только там ручка с двумя положениями).
Большая работа проделана и описана, спасибо!
Проблема известная (к счастью, редким "счастливчикам"). Даже открытый issue есть на эту тему, где обсуждается, в том числе, и описанное тут решение.
Оставлю ссыль, вдруг кому пригодится для отслеживания статуса https://github.com/dotnet/runtime/issues/71921
В какой-то момент его могут закрыть и проблема может стать неактуальной
с сокетами тоже есть нюансы - https://github.com/dotnet/runtime/issues/72153
Почему на Linux он дороже?
А черт его знает. Он просто по-другому работает, и всё тут. Не хуже, не лучше — иначе. И по-хорошему поведение dotnet-а, его использование таких примитивов, нужно настраивать разными способами в зависимости от ОС.
Нет, этот семафор в Windows не просто по-другому работает. Он - речь идет о классе LowLevelLifoSemaphore - в Windows и в Unix по-разному реализован. Сам по себе LowLevelLifoSemaphore в LowLevelLifoSemaphore.cs (лежит там же, рядом с PortableThreadPool.WorkerThread.cs на который ведет ссылка) описан как partial class, то есть, содержит только часть кода. А другая часть лежит в других, разных для разных ОС файлах: LowLevelLifoSemaphore.Windows.cs и LowLevelLifoSemaphore.Unix.cs. И в реализации для Windows никакого SpinLock нет. Там используется I/O Completion Port - есть в Windows, начиная с Win2K, такой объект ядра, который предназначен специально для управления пулом потоков. То есть, тем, чтобы найти поток для выполнения нагрузки в пуле, занимается ядро. А в *nix такого объекта нет (и AFAIK нет даже аналога). И там нужное поведение реализуется через LowLevelMonitor, где, по-видимому, и используется SpinLock, Так что насчет "настраивать разными способами в зависимости от ОС" в .NET все нормально: так и сделано.
Спасибо за более точное описание различия.
то есть как "нет даже аналога"? epoll уже десятки лет сушествует, kevent есть. Это у авторов дотнета может быть руки не дошли нормально сделать, потом выкатят думаю с помпой "мы всё тут ускорили вам"
Чем тут epoll поможет? Или kqueue, коей kevent является частью. Тут речь ведь ведь не о вводе/выводе вообще, тем более - специфическом для сокетов. Ну да слово "I/O" в названии "I/O Completion port" (он же IOCP) вводит в замешательство, но конкретно тут IOCP используется чисто как сущность планировщика, с вводом выодом не связанная: каждый поток из пула ожидает своей очереди выполниться, сделав внутри LowLevelLifoSemaphore.WaitCore вызов GetQueuedCompletionStatus, а на выполнение он запускается вызовом PostQueuedCompletionStatus в LowLevelLifoSemaphore.ReleaseCore. А какой поток запустить, выбирает непосредственно ядро. И никаких дескрипторов файлов тут нет. Чем это можно заменить в *nix? Именно в плане выбора потока из пула по запросу пользовательского режима?
Ну, а ещё тут имеет место быть пресловутая diversity, свойственная Unix с давних времен: ещё Эд Пост более сорока лет назад в своей статье про настоящих программистов мимходом упомянул, что "типичный кодокопатель(hacker) Unix не знает, как на этой неделе вызывается команда print". То есть, там в *nix всё по-разному: kqueue, который можно как-то и для чисто пользовательских событий приспособить- это в xBSD, в Linux - что-то свое, а в Solaris даже свой вариант IOCP есть. Ну, и как с этим жить, чтобы "сделать нормально"?
Насколько мне известно, с помощью epoll как раз и эмулируются io completion ports в wine, а вот обратное сильно затруднительно.
Что там в Wine я не знаю, а в Windows (современной и ее родоначальнике Windows NT) epoll эмулировать совершенно без надобности: в NT весь ввод-вывод в файлы (любые, и сокеты - тоже) базового асинхронный. а синхронный ввод вывод там реализован вторичным образом: запуском асинхронной операции и ожиданием ее завершения на описателе соответствующего файла. Так что костыль типа epoll для нее не требуется. Вот в Win3.x/9x костыль требовался и там он был (функции Winsock с префиксом WSA).
И был ввод/вывод в NT асинхронным изначально, ещё когда NT Windows не была. Такую вот продвинутую систему делала на основе своего предыдущего опыта команда родом из DEC для IBM и Microsoft (изначально NT была их совместным проектом).
Но тут речь была не об этом, а о конкретном (названия файлов - выше, исходники .NET открыты) коде из .NET Core library: в нем никаких сокетов нет. А вообще, если вы знаете, как лучше сделать - расскажите, как это сделать конкретно, например - на основе упомянутого вами кода Wine, а не наводите тень на плетень.
Там вроде все сложнее - там используется обычный спинвейт (который одинаков для всех платформ, просто цикл (с определенной в рантайме длительностью) из pause инструкции (yield на арме). Если спиннеров слишком много, то уже переходит на те самые wait-еры которые уже различаются между Windows и nix. Переменная, которую автор поста использовал вроде как регулирует кол-во кросплатформенных спиннеров.
По спиннерам надо помнить что они очень неэффективны на arm64 где они по сути действительно грузят проц на 100%. На х64 pause инструкция более лайтовая (особенно в сочитании с Hyper Threading).
Ещё могу порекомендовать поставить переменную DOTNET_SYSTEM_NET_SOCKETS_THREAD_COUNT в 1 для снижения CPU usage (кол-во тредов крутящих epoll)
Information
- Website
- tech.kontur.ru
- Registered
- Founded
- Employees
- over 10,000 employees
- Location
- Россия
- Representative
- Диана
Тредпульное заклятие Dotnet-демонов на Linux