Узкое место с резьбой NetworkStream - PullRequest
1 голос
/ 06 ноября 2019

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

Я совсем новичок в разработке бэкэнда. В прошлом месяце, с 20000 ответов в секунду до 80000, я многому научился и применил их к этому серверу: предварительное выделение буфера byte [], вызовы API non-alloc, поток данных TPL, Threading.

В основномвся обработка теперь перемещается в конвейер обработки, сеть, принимающая / отправляющая, находится в отдельном потоке.

Однако общая производительность по-прежнему ограничена сетевым вводом-выводом, который реализован с помощью TCPClient.

Реализация

  • Используемый метод NetworkStream R / W является блокирующим. Сначала прочитайте 4 байта, чтобы получить длину, 2 байта для заголовка, затем N байтов для тела сообщения.
  • Я использую объект обертки буфера byte [], названный BufferSegmentPointer . Эти объекты предварительно инициализируются с помощью байта [] определенной длины (> 2048).
  • BufferSegmentPointer передаются по конвейеру потока данных TPL, после обработки запроса результатом является буфер.BlockCopy () в буфер, в него заносится используемый диапазон byte [], затем он отправляется в NET OUTPUT через конвейер, поэтому NetworkStream.Write () просто знает, что писать.
  • Сегмент обработки конвейера представляет собой TransformBlock с MaxDegreeOfParallism, установленным в Enviromnet.ProcessorCount - 2.

Тестовая статистика

Программа представляет собой просто конвейер с IN > ОБРАБОТКА > OUT .

, которые можно разбить на 5 сегментов:

NET IN > INПередача > ОБРАБОТКА > Выход Передача > NET OUT

Отмечено, что следующий результат теста ВМ ограничен из-зак тестовому клиенту на ВМ не может отправить достаточно запросов. Это мое узкое место. Поток ввода-вывода NetworkStream слишком медленный.

Заметил, что если я объединю десятки запросов в один вызов NetworkStream.Write (), он будет работать заметно лучше. , но это бессмысленно, потому что запросы никогда не связываются в реальном производственном сценарии.

Заметил, что если я просто напишу байт 2 ГБ [] из тестового клиента и получу его с сервера, это будет стоить 0,8 секунды. (Все тесты, упомянутые в этом посте, выполняются на локальном хосте.)


Тест 1: TCP I

1000000 Запрос 702 байта, отправленный тестовым клиентом вцикл.

Секундомер запускается при первом получении, останавливается при получении всех.

O> X> X> X> X (не передается через поток данных, считается после получения)

Результат

  • 12.99 с на ESXi VM 2,4 ГГц * 12
  • 5,86 с на i5-7500 3,4 ГГц * 4

Тест 2: TCP O

Для проверки пропускной способности записи.

1000000 Вызов NetworkStream.Write ()из N байт.

Секундомер запускается непосредственно перед первой отправкой, останавливается при отправке всех.

X> X> X> X> O

Результат

  • 8,42 с N = 653 на ESXi VM 2,4 ГГц * 12
  • 4,75 с N = 577, вклi5-7500 3,4 ГГц * 4

Тест 3: TCP IO

1000000 Запрос 702 байта, отправленный тестовым клиентом в цикле.

1000000 ответ, 500 ~ 700 байт, поскольку используется случайный результат.

Секундомер запускается непосредственно перед первым запросом, отправленным тестовым клиентом, останавливается при получении всех ответов.

O> X> X> X> O
(Запрос не передан черезПоток данных, считается после получения)
(Ответ создан и преобразован в байт [] заранее, просто запишите его один раз при получении запроса)

Результат

  • 11,7 с На ESXi VM 2,4 ГГц * 12
  • 5,74 на i5-7500 3,4 ГГц * 4

Тест 4: Локальная обработка

1000000 запрос 702 байта, сгенерированный сервером сам.

Нет ответов, после выполнения вызова обработки счетчик ++.

Секундомер запускается непосредственно перед подачей первого запроса в конвейер, останавливается, когда обработчик достигает 1000000.

X> O> O> X> X

Результат

  • 6,89 с на ESXi VM 2,4 ГГц * 12
  • 8,28 с на i5-7500 3,4 ГГц *4

Тест 5: Производственный тест

Обработка включена. 1000000 запросов, 702 байта, отправленных тестовым клиентом в цикле.

1000000 ответ, 500 ~ 700 байт, поскольку используется случайный результат.

Секундомер запускается непосредственно перед первым запросом, отправленным тестовым клиентом, останавливается, когда все ответы получены.

O> O> O> O> O

Результат

  • 18,6 с на ESXi VM 2,4 ГГц * 12
  • 11,8 с на i5-7500 3,4 ГГц *4

Мои вопросы

  1. Это нормальная пропускная способность <100 Мбит / с? </li>
  2. Могут ли помочь неблокирующие (асинхронные) вызовы?
  3. Возможно ли, что SAEA (SocketAsyncEventArgs) сможет побить TCPClient? Я видел, что методы сокетов будут пытаться использовать SAEA, если это возможно, и TCPClient - просто оболочка ......
  4. Есть ли у вас предположение, что может быть узким местом?
  5. Мой супервизор может отправить более 300000 / с запросов (> 200 Мбит / с) с JAVA, действительно ли это JAVA превосходит производительность .NET, или это глупость?
  6. Благодаря тому, что NetworkStream R / W работает лучшена моем офисном ПК (i5) по сравнению с виртуальной машиной я считаю, что NetworkStream ограничена производительностью одного ядра (потока) (верно ли это предположение?), но мне нужно знать, почему это так медленно? Это должно быть так медленно?
...