Разработка CSO медленно, но уверенно продвигается к своему логическому завершению. И если изначально CSO задумывалась как библиотека для реализации многопоточного асинхронного TCP-сервера, то в процессе разработки она трансформировалась в библиотеку многопоточной асинхронной обработки сообщений с поддержкой работы с файловыми дескрипторами (в том числе со специальными классами для реализации TCP-сервера). Классы связанные с TCP-соединениями и таймера еще не полностью закончены, а вот основная часть уже полностью готова. Недавно я уже проводил сравнение различных интерфейсов передачи сообщений в Linux. Теперь пришло время протестировать скорость обработки сообщений. Для проведения тестирования был избран сценарий обработки echo-сообщений, подробнее о котором далее.
Изначально в очереди сообщений располагается N сообщений, содержащих в полезной нагрузке одно единственное число – TTL. При обработке такого сообщения обработчик смотрит на значение TTL и если оно больше единицы генерирует новое сообщение TTL у которого на единицу меньше чем у полученного. Если же TTL полученного сообщения равен 1 – распространение этого сообщения прекращается. Тест считается завершенным, когда распространение всех сообщений прекращается. Для случая с использованием нескольких адресатов адресат каждого сообщения выбирается случайным образом. При синхронизации доступа к объектам гарантируется следующее: данный объект в данный момент времени обрабатывает только одно сообщение.
Теперь рассмотрим более подробно наши тесты:
- Одиночный объект – тест представляет из себя простую передачу сообщений одному единственному объекту-обработчику без какой-либо синхронизации. Мало того, такой объект-обработчик является экземпляром класса, унаследованного от CCSO_MsgProcessor и как следствие имеет собственную очередь сообщений и собственные рабочие потоки.
- Множество объектов – для этого теста создаётся множество объектов-обработчиков унаследованных от класса CCSO_Object. Такие объекты не имеют собственной очереди сообщений и рабочих потоков, поэтому все сообщения передаются через обработчик сообщений (CCSO_MsgProcessor). Для адресации получателя сообщения используется не его адрес, а специальный идентификатор, позволяющий избегать проблем при обработке сообщений, адресаты которых уже удалены. Непосредственное преобразование идентификатора объекта в его адрес делается диспетчером объектов (CCSO_ObjectDispatcher) перед обработкой каждого сообщения.
- Множество объектов с синхронизацией – этот тест отличается от предыдущего только тем, что диспетчером объектов обеспечивается обработка только одного сообщения для конкретного адресата в момент времени. Такая организация доступа к объектам полностью снимает и те немногие проблемы доступа к удалённым объектам, которые есть у предыдущего варианта.
- Множество объектов для работы с файловыми дескрипторами – отличается от 3-го теста тем, что используется другой базовый класс объекта (CCSO_FDObject) и другой обработчик сообщений (CCSO_FDObjectMsgProcessor). Данный обработчик сообщений позволяет принимать сообщения не только через свою очередь, но и через EPoll, благодаря чему может получать события и от файловых дескрипторов (в том числе и от сокетов). Однако для совместимости с EPoll встроенная очередь сообщений такого обработчика использует не Futex, а EventFD для ожидания данных.
- Множество объектов для работы с файловыми дескрипторами с синхронизацией – Собственно именно таким методом и происходит обработка сетевых подключений.
Тестирование производилось на следующей машине: Core i7 980 @ 4.3Ghz (hyperthreading отключён), 24GB DDR3-1333 RAM, Windows 7, виртуальная машина VMWare c установленной на неё CentOS 6.3, и со следующими условиями: кол-во изначально размещаемых сообщений – 1024, начальный TTL – 65536, кол-во объектов – 1024, и завершилось следующими результатами:
Теперь как обычно выводы. Первое что бросается в глаза это довольно значительное падение производительности при использовании 2-х потоков вместо одного (кроме случаев использования EPoll), с дальнейшим стабильным ростом производительности при добавлении новых потоков. Далее, использование диспетчера объектов (преобразование ID объекта в указатель) снижает производительность примерно на 43%, а использование синхронизации доступа даёт еще -31% производительности. Также очень чётко видна огромная цена за использование механизма очереди совместимого с EPoll и его самого. Производительность падает в 3.86 и 2.82 раза для случаев без синхронизации и с ней соответственно (к слову при работе с EPoll плата за синхронизацию составляет всего чуть менее 5%, а не 31% как при использовании futex именно из-за огромных накладных расходов на саму передачу сообщения). Также добавлю, что отказ от инструкций SSE4.2 и переход на инструкции SSSE3 замедляет все тесты кроме первого (тесты где используется диспетчер объектов) в среднем на 4,6%.
Небольшой бонус
В качестве бонуса предлагаю результаты сравнения производительности тестов 3 и 5 с реализацией подобной задачи на Erlang`е (любезно предоставленной kvakvs`ом с GameDev.ru):
P.S. Для увеличения скорости обработки сообщений можно прибегнуть к ухищрению и вместо создания очереди сообщений совместимой с EPoll просто создать отдельный поток для конвертирования сообщений EPoll в сообщения CSO.