Я знаю, что эту тему уже иногда задают, и я прочитал почти все темы и комментарии, но так и не нашел ответа на свою проблему.
Я работаю над высокопроизводительной сетевой библиотекой, которая должна иметь TCP-сервер и клиент, должна иметь возможность принимать даже 30000+ подключений, а пропускная способность должна быть максимально высокой.
Я очень хорошо знаю, что мне нужно использовать async
методы, и я уже реализовал все найденные и протестированные решения.
В моем бенчмаркинге использовался только минимальный код, чтобы избежать каких-либо накладных расходов в области видимости, я использовал профилирование, чтобы минимизировать нагрузку на ЦП, больше нет места для простой оптимизации, на принимающем сокете буфер данные всегда читались, подсчитывались и отбрасывались, чтобы избежать полного заполнения буфера сокета.
Дело очень простое, один TCP Socket слушает на локальном хосте, другой TCP Socket подключается к слушающему сокету (из той же программы, на той же машине oc.), затем один бесконечный цикл начинает отправлять < Strong>256kB пакеты размером с клиентского сокета в сокет сервера.
Таймер с интервалом 1000 мс печатает счетчик байтов из обоих сокетов в консоль, чтобы сделать пропускную способность видимой, а затем сбрасывает их для следующего измерения.
Я понял, что оптимальный размер пакета составляет 256 КБ, а размер буфера сокета составляет 64 КБ, чтобы обеспечить максимальную пропускную способность.
С помощью методов типа async/await
я мог достичь
~370MB/s (~3.2gbps) on Windows, ~680MB/s (~5.8gbps) on Linux with mono
С помощью методов типа BeginReceive/EndReceive/BeginSend/EndSend
я мог достичь
~580MB/s (~5.0gbps) on Windows, ~9GB/s (~77.3gbps) on Linux with mono
С помощью методов типа SocketAsyncEventArgs/ReceiveAsync/SendAsync
я мог достичь
~1.4GB/s (~12gbps) on Windows, ~1.1GB/s (~9.4gbps) on Linux with mono
Проблемы следующие:
async/await
методы оказались самыми медленными, поэтому я не буду с ними работатьBeginReceive/EndReceive
методы запускали новый асинхронный поток вместе сBeginAccept/EndAccept
методами, под Linux/mono каждый новый экземпляр сокета работал очень медленно (когда не было больше потока вThreadPool
mono запускались новые потоки, но для создание 25 экземпляров соединений заняло около 5 минут, создание 50 соединений было невозможно (программа просто перестала что-либо делать после ~30 соединений).- Изменение размера
ThreadPool
вообще не помогло, да и менять я бы его не стал (это был просто отладочный ход) - На данный момент лучшим решением является
SocketAsyncEventArgs
, и это дает самую высокую пропускную способность в Windows, но в Linux/mono он медленнее, чем Windows, а раньше было наоборот.
Я сравнил свою машину с Windows и Linux с помощью iperf,
Windows machine produced ~1GB/s (~8.58gbps), Linux machine produced ~8.5GB/s (~73.0gbps)
Странно то, что iperf
может дать более слабый результат, чем мое приложение, но в Linux он намного выше.
Прежде всего, я хотел бы знать, нормальные ли результаты, или я могу получить лучшие результаты с другим раствором?
Если я решу использовать методы BeginReceive/EndReceive
(они дали относительно высокий результат в Linux/mono), то как я могу решить проблему с потоками, сделать экземпляр подключения быстрым и устранить зависание после создания нескольких экземпляров?
Я продолжаю делать дальнейшие тесты и поделюсь результатами, если будут какие-то новые.
================================= ОБНОВЛЕНИЕ ================ ==================
Я обещал фрагменты кода, но после многих часов экспериментов в целом код получился беспорядочным, поэтому я просто поделюсь своим опытом, если он может кому-то помочь.
Мне пришлось понять, что в Windows 7 петлевое устройство работает медленно, не смог получить результат выше 1 ГБ/с с iperf или NTttcp, только Windows 8 и более новые версии имеют быструю петлю, поэтому меня больше не интересуют результаты Windows, пока я не смогу протестировать более новую версию. SIO_LOOPBACK_FAST_PATH должен быть включен через Socket.IOControl, но выдает исключение в Windows 7.
Оказалось, что наиболее эффективным решением является событие завершения на основе SocketAsyncEventArgs. реализация как в Windows, так и в Linux/Mono. Создание нескольких тысяч экземпляров клиентов никогда не портило ThreadPool, программа не останавливалась внезапно, как я упоминал выше. Эта реализация очень удобна для многопоточности.
Создание 10 подключений к прослушивающему сокету и подача данных из 10 отдельных потоков из ThreadPool
вместе с клиентами может привести к ~2GB/s
трафику данных в Windows и ~6GB/s
в Linux/Mono.
Увеличение количества клиентских подключений не улучшило общую пропускную способность, но общий трафик стал распределяться между подключениями, это могло быть связано с тем, что загрузка ЦП составляла 100% на всех ядрах/потоках даже при 5, 10 или 200 клиентах.
Я думаю, что общая производительность неплохая, 100 клиентов могут производить около ~500mbit/s
трафика каждый. (Конечно, это измеряется локальными подключениями, реальный сценарий в сети будет другим.)
Единственное наблюдение, которым я хотел бы поделиться: экспериментирование как с размерами входного/выходного буфера Socket, так и с размерами буфера чтения/записи программы/циклами цикла сильно повлияло на производительность и сильно по-разному в Windows и Linux/Mono.
В Windows наилучшая производительность достигается при использовании буферов 128kB socket-receive
, 32kB socket-send
, 16kB program-read
и 64kB program-write
.
В Linux предыдущие настройки давали очень низкую производительность, но 512 КБ socket-receive and -send
для обоих размеров буфера, 256kB program-read
и 128kB program-write
работали лучше всего.
Теперь моя единственная проблема заключается в том, что если я попытаюсь создать 10000 соединительных сокетов, примерно после 7005 он просто перестанет создавать экземпляры, не выдает никаких исключений, и программа работает, так как не было никаких проблем, но я не знаю, как это может выйти из определенного цикла for
без break
, но это так.
Буду признателен за любую помощь в отношении всего, о чем я говорил!