При переработке CatServerObjects столкнулся с необходимостью выбора механизма передачи сообщений между потоками приложения. Для требуемой очереди были сформированы следующие требования:
- FIFO порядок обработки
- Поддержка сообщений различных размеров, но с возможностью наличия верхней границы
- Поддержка блокировки потока на время ожидания данных
- Поддержка работы с EPoll
Рассматривались следующие варианты:
- UNIX-сокеты
- POSIX Message Queue
- System V IPC Message Queue
- Собственная реализация очереди сообщений
Вариант номер 3 пришлось сразу отбросить, т.к. он не совместим с EPoll, но в тестах он всё равно будет представлен для сравнения.
Собственная очередь сообщений
Перед создаваемой очередью сообщений ставились следующие требования:
- Предельная быстрота при передаче указателей
- Lockfree-алгоритм
- FIFO порядок обработки
- Ожидания свободного места при записи в спинлоке
- При ожидании данных возможность ожидать как в спинлоке, так и в режиме блокировки потока
- Поддержка работы с EPoll
Для поддержки блокировки потока для ожидания данных рассматривались 2 варианта: Futex и EventFD. Сразу стоит отметить что вариант с использованием Futex несовместим с использованием EPoll, а потому годится только для потоков обработки внутренних сообщений. Ниже приводится сравнение производительности очереди сообщения при различных методах ожидания данных:
Сразу хочется отметить что ожидание данных с помощью futex`а и spinlock`а даёт очень большой разброс в производительности. Она всегда выше чем у EventFD, но минимумы и максимумы могут различаться и в 2 раза. Т.к. эти разбросы весьма случайны, то я думаю они вызваны взаимных расположением точек выполнения у потоков во время теста.
Т.к. создаваемая очередь могла передавать только указатели то в дополнение к ней был разработан специальный пул из которого выделялась память для сообщений. Такой подход вообще имеет 2 больших плюса: отсутствие лишних копирований тела сообщения и предельное сокращение времени постановки сообщения в очередь (записывается всего 1 указатель).
Тестирование различных API
Для каждого из стандартных API предоставляемых Linux для обмена сообщениями тестировалась как передача самого сообщения, так и передача указателя на него. Использование указателя на сообщение хоть и уменьшает кол-во ненужных копирований данных, но зато даёт дополнительные расходы на выделение/освобождение памяти.
Во-первых сразу хочу отметить очень высокую цену, которую мы платим за блокирование потока при ожидании данных при большом кол-ве потоков. Во вторых нельзя не заметить, что использование указателей для стандартных интерфейсов начинает приносить стабильный прирост производительности только начиная с сообщений размером 1-2КБ.
Из полученных результатов можно сделать следующие выводы:
- UNIX-сокеты вообще не годятся для обмена сообщениями внутри программы
- POSIX-очереди сообщений практически идентичны SysV-очередям, но зато обладают поддержкой работы с EPoll, а кроме того, еще и приоритетами для передаваемых сообщений.
- Использование EventFD для ожидания данных во всех случаях, кроме 1-го писателя и 1-го читателя, даёт как минимум 2-х кратный выигрыш в скорости.
- Использование Futex не очень сильно сказывается на производительности очереди при более чем 1-м писателе и 1-м читателе
- Очень высокая нестабильность результатов своей реализации очереди сообщений (о возможных причинам я уже писал выше)
Использование пропускной способности памяти
В качестве бонуса приведу диаграмму пропускной способности очереди сообщений для 3-х писателей и 3-х читателей:
P.S. Скорость очистки памяти на тестируемом компьютере порядка 15-16ГБ/с. Все значения сверх этой цифры свидетельствуют о том, что обмен идёт через L3-кеш процессора.