Подтвердить что ты не робот

Сокеты .NET с сокетами С++ с высокой производительностью

Мой вопрос - разрешить аргумент с моими сотрудниками на С++ vs С#.

Мы реализовали сервер, который получает большое количество потоков UDP. Этот сервер был разработан на С++, используя асинхронные сокеты и перекрывающиеся ввода-вывода с использованием портов завершения. Мы используем 5 портов завершения с 5 потоками. Этот сервер может легко обрабатывать пропускную способность 500 Мбит/с в гигабитной сети без потери пакетов/ошибок (мы не проталкивали наши тесты дальше 500 Мбит/с).

Мы попытались повторно реализовать тот же тип сервера на С#, и нам не удалось достичь той же входящей пропускной способности. Мы используем асинхронный прием с использованием метода ReceiveAsync и пула SocketAsyncEventArgs, чтобы избежать накладных расходов на создание нового объекта для каждого приема. Каждый SAEventArgs имеет буфер, установленный для него, поэтому нам не нужно выделять память для каждого приема. Бассейн очень, очень большой, поэтому мы можем поставить в очередь более 100 запросов на получение. Этот сервер не способен обрабатывать входящую пропускную способность более 240 Мбит/с. По этому ограничению мы теряем некоторые пакеты в наших UDP-потоках.

Мой вопрос в следующем: следует ли ожидать такой же производительности с использованием сокетов С++ и сокетов С#? Мое мнение таково, что он должен быть такой же, если в .NET правильно управлять памятью.

Боковой вопрос: кто-нибудь знает хорошую статью/ссылку, объясняющую, как сокеты .NET используют порты завершения ввода/вывода под капотом?

4b9b3361

Ответ 1

Кто-нибудь знает хорошую статью/ссылку, объясняющую, как .NET-сокеты используют порты завершения ввода-вывода под капотом?

Я подозреваю, что единственной ссылкой будет реализация (т.е. рефлектор или другой компилятор сборки). С этим вы обнаружите, что все асинхронные IO проходят через порт завершения ввода-вывода с обратными вызовами, обрабатываемыми в пуле IO-потоков (который является отдельным для пула обычных потоков).

использовать 5 портов завершения

Я бы ожидал использовать один порт завершения, обрабатывающий все IO в один пул потоков с одним потоком на завершение обслуживания пула (предполагая, что вы выполняете любое другое IO, включая диск, асинхронно).

Несколько портов завершения будут иметь смысл, если у вас есть некоторая форма приоритизации.

Мой вопрос заключается в следующем: следует ли ожидать такой же производительности с использованием сокетов С++ и сокетов С#?

Да или нет, в зависимости от того, насколько узко вы определяете часть "using... socket". Что касается операций с начала асинхронной операции до тех пор, пока завершение не будет отправлено на порт завершения, я бы не ожидал существенной разницы (вся обработка выполняется в Win32 API или ядре Windows).

Однако безопасность, которую предоставляет среда выполнения .NET, добавит некоторые накладные расходы. Например. длина буферов будет проверена, делегированы и т.д. Если предел для приложения - это CPU, то это, скорее всего, изменит ситуацию, и в крайнем случае небольшая разница может легко складываться.

Также версия .NET иногда приостанавливается для GC (.NET 4.5 делает асинхронную коллекцию, поэтому в будущем это улучшится). Существуют методы, позволяющие свести к минимуму накопление мусора (например, повторно использовать объекты, а не создавать их, использовать структуры, избегая при этом бокса).

В конце концов, если версия С++ работает и отвечает вашим потребностям в производительности, почему порт?

Ответ 2

Вы не можете сделать прямой порт кода с С++ на С# и ожидать той же производительности..NET делает намного больше, чем С++, когда дело доходит до управления памятью (GC), и убедитесь, что ваш код безопасен (пограничные проверки и т.д.).

Я бы выделил один большой буфер для всех операций ввода-вывода (например, 65535 x 500 = 32767500 байт), а затем назначил кусок для каждого SocketAsyncEventArgs (и для операций отправки). Память дешевле, чем у процессора. Используйте диспетчер буферов /factory, чтобы предоставить куски для всех подключений и операций ввода-вывода (шаблон Flyweight). Microsoft делает это в своем примере Async.

Оба метода Begin/End и Async используют порты завершения ввода-вывода в фоновом режиме. Последнему не нужно выделять объекты для каждой операции, что повышает производительность.

Ответ 3

Я предполагаю, что вы не видите такой же производительности, потому что .NET и С++ на самом деле делают разные вещи. Ваш код на С++ может быть не таким безопасным или проверить границы. Кроме того, вы просто измеряете возможность получения пакетов без какой-либо обработки? Или ваша пропускная способность включает время обработки пакета? Если это так, то код, который вы, возможно, написал для обработки пакетов, может быть не таким эффективным.

Я бы предложил использовать профилировщик, чтобы проверить, сколько времени расходуется, и пытаться его оптимизировать. Фактический код сокета должен быть довольно эффективным.