Сокеты .NET и C ++ на высокой производительности - PullRequest
45 голосов
/ 11 декабря 2011

Мой вопрос состоит в том, чтобы уладить спор с моими коллегами по C ++ против C #.

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

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

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

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

Ответы [ 3 ]

8 голосов
/ 11 декабря 2011

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

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

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

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

Многократные порты завершения имели бы смысл, если бы у вас была какая-то форма приоритетов.

Мой вопрос таков: следует ли ожидать одинаковой производительностииспользовать сокеты C ++ и сокеты C #?

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

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

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

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

6 голосов
/ 11 декабря 2011

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

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

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

1 голос
/ 11 декабря 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...