Я попытался провести эксперимент, в котором я создал простую программу «Производитель / Потребитель». Они запускаются в отдельных потоках. Производитель генерирует некоторые данные, а потребитель собирает их в другом потоке. Задержка обмена сообщениями, которую я достиг, составляет приблизительно 100 наносекунд. Кто-нибудь может сказать мне, если это разумно или есть значительно более быстрые реализации там?
Я не использую замки … просто счетчики памяти. Мой эксперимент описан здесь:
http://tradexoft.wordpress.com/2012/10/22/how-to-move-data-between-threads-in-100-nanoseconds/
Обычно потребитель ожидает увеличения счетчика, а затем вызывает функцию-обработчик. Так что на самом деле не так много кода. Тем не менее я был удивлен, что это заняло 100 нс.
Потребитель выглядит так:
void operator()()
{
while (true)
{
while (w_cnt==r_cnt) {};
auto rc=process_data(data);
r_cnt++;
if (!rc)
break;
}
}
Производитель просто увеличивает w_cnt, когда у него есть доступные данные.
Есть ли более быстрый способ?
Я полагаю, что ваша задержка — это результат того, как операционная система планирует переключение контекста, а не саму спин-блокировку, и я сомневаюсь, что вы многое можете с этим поделать.
Однако вы можете перемещать больше данных одновременно, используя кольцевой буфер. Если один поток пишет, а другой поток читает, вы можете реализовать кольцевой буфер без блокировок. По сути это был бы тот же подход спин-блокировки (ожидание до tailidx != headidx
), но производитель может закачать более одного значения в буфер, прежде чем оно будет передано потребителю. Это должно улучшить общее время ожидания (но не однозначное время ожидания).
Если ваши потоки выполняются на разных ядрах, самый быстрый способ «отправить сообщение» из одного потока в другой — барьер записи.
Когда вы пишете в какую-то область памяти, вы на самом деле пишете в процессоры записывают в буфер, не в место основной памяти. Буфер записи периодически сбрасывается в основную память процессором. Кроме того, инструкция записи может быть отложена, когда происходит переупорядочение команд. Когда происходит фактическая запись в основную память, протокол когерентности кэша вступает в игру и «информирует» другой процессор об обновлении местоположения памяти. После этого другой процессор лишает законной силы строку кэша, и другой поток сможет видеть ваши изменения.
Сохраните процессор барьерной силы, чтобы очистить буфер записи и запретить переупорядочение команд, и ваша программа сможет отправлять больше сообщений в секунду.