Подтвердить что ты не робот

Как 2 потока могут использовать одну и ту же строку кеша

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

Я не эксперт по производительности, и поэтому я решил задать этот вопрос здесь. Пользовательская библиотека поставляется с определенным ограничением, описанным ниже:

"Callee не должен вызывать какой-либо из API библиотеки в контексте потока обратного вызова. Если они попытаются это сделать, поток будет зависание"

Единственный способ преодолеть ограничение API - это запустить другой поток, который обрабатывает сообщение и вызывает библиотеку для отправки ответа. Поток библиотеки и поток процессов будут иметь общую очередь, которая будет защищена мьютексом и использовать вызовы wait_notify() для указания наличия сообщения.

Если я получаю 80 тыс. сообщений в секунду, то я бы помещал потоки в сон и часто их разбудил, выполняя переключатели контекста потока ~ 80 тыс. раз в секунду.

Кроме того, поскольку есть два потока, они не будут использовать буфер сообщений в кеше L1. Линия кэша, содержащая сообщение, сначала будет заполнена потоком библиотеки, а затем выведена и вытащена в кеш L1 потока процессов. Я что-то упустил или, возможно, дизайн библиотеки не предназначен для высокопроизводительных случаев использования?

Мои вопросы:

  • Я видел предупреждения типа "Не используйте этот API в контексте обратного вызова, поскольку он может вызвать блокировки". во многих библиотеках. Каковы общие варианты дизайна, которые вызывают такие конструктивные ограничения? Они могут использовать рекурсивные блокировки, если это простой вопрос одного потока, вызывающий блокировку несколько раз. Является ли это проблемой повторного участия и какие проблемы могут заставить владельца API сделать API без повторного входа?

  • Есть ли способ в приведенной выше модели проектирования, где поток библиотеки и поток процессов могут совместно использовать одно и то же ядро ​​и, следовательно, совместно использовать строку кэша?

  • Насколько дорогим является volatile sig_atomic_t как механизм совместного использования данных между двумя потоками?

  • Учитывая высокочастотный сценарий, какой легкий способ обмена информацией между двумя потоками?

Библиотека и мое приложение построены на С++ и Linux.

4b9b3361

Ответ 1

Как два потока могут использовать одну и ту же строку кэша?

Нити не имеют ничего общего с линиями кеша. По крайней мере, неявно. Что может пойти не так, как сбросить кеш при переключении контекста и недействительности TLB, но при том же сопоставлении виртуальных адресов для потоков кеши обычно не обращают внимания на эти вещи.

Каковы общие варианты дизайна, которые вызывают такие конструктивные ограничения?

Реализации библиотеки не хотят иметь дело с:

  • Комплексные схемы блокировки.
  • Логика повторного входа (т.е. вы вызываете "send()", библиотека вызывает вас с помощью on_error(), из которого вы вызываете send() снова), что потребует особого внимания к ним).

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

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

Рекурсивные мьютексы относительно дороги. Люди, которые заботятся об эффективности времени выполнения, как правило, избегают их там, где это возможно.

Есть ли способ в приведенной выше модели проектирования, где поток библиотеки и поток процессов могут совместно использовать одно и то же ядро ​​и, следовательно, совместно использовать строку кэша?

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

Насколько дорогим является volatile sig_atomic_t как механизм совместного использования данных между двумя потоками?

Само по себе это не дорого. Тем не менее, в многоядерной среде вы можете использовать недействительность кэша, киоски, увеличенный трафик MESI и т.д. Учитывая, что оба потока находятся на одном ядре, и ничто не вторгается - единственное наказание не в состоянии кэшировать переменную, это нормально, так как он не должен кэшироваться (т.е. компилятор всегда извлекает его из памяти, будь то кэш или основная память).

Учитывая высокочастотный сценарий, какой легкий способ обмена информацией между двумя потоками?

Чтение и запись из/в ту же память. Возможно, без каких-либо системных вызовов, блокировки вызовов и т.д. Например, можно реализовать кольцевые буферы с двумя параллельными потоками с использованием барьеров памяти и не более того, для архитектуры Intel, по крайней мере. Чтобы сделать это, вы должны быть предельно осторожны. Если, однако, что-то должно быть явно синхронизировано, то атомарные инструкции являются следующим уровнем. Haswell также поставляется с Transactional Memory, который может использоваться для низкой скорости служебной информации. После этого ничего не происходит быстро.

Кроме того, ознакомьтесь с Руководством разработчика Intel Architectures, глава 11, о кеше и элементе памяти.

Ответ 2

Важно помнить, что при работе над сетевыми приложениями более важной метрикой производительности является "задержка для каждой задачи", а не пропускная способность всего процессора в целом. С этой целью очереди сообщений потоков являются очень хорошим методом для быстрого реагирования на активность.

80 тыс. сообщений в секунду на сегодняшней серверной инфраструктуре (или даже моем ноутбуке Core i3) граничит с незначительной территорией - особенно в отношении производительности кэша L1. Если потоки выполняют значительную работу, то это не является необоснованным вообще ожидать, что CPU будет очищаться через кеш L1 каждый раз, когда будет обработано сообщение, и если сообщения не работают очень много, тогда это просто не имеет значения, потому что он, вероятно, будет составлять менее 1% загрузки ЦП, независимо от политики L1.

При такой скорости обмена сообщениями я бы рекомендовал использовать пассивную модель потоковой передачи, например. где потоки пробуждаются, чтобы обрабатывать сообщения, а затем засыпать. Это даст вам наилучшую модель с задержкой - vs-performance. Например, это не самый эффективный с точки зрения эффективности метод, но он будет лучше всего реагировать на сетевые запросы (что обычно является тем, что вы хотите использовать при программировании сети).

На сегодняшних архитектурах (2,8 гГц, 4+ ядрах) я даже не стал бы беспокоиться о сырой производительности, если бы не ожидал, что будет обрабатывать, возможно, 1 миллион сообщений в секунду в секунду. И даже тогда, это будет немного зависеть от того, сколько Real Work должны выполнять сообщения. Это не ожидается, что он сделает гораздо больше, чем подготовка и отправку некоторых пакетов, тогда 1 миллион определенно консервативен.

Есть ли способ в приведенной выше модели проектирования, где поток библиотеки и поток процессов могут совместно использовать одно и то же ядро ​​и, следовательно, совместно использовать строку кэша?

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

Учитывая высокочастотный сценарий, какой легкий способ обмена информацией между двумя потоками?

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