Лучший способ синхронизации потоков и измерения производительности на частоте менее микросекунды - PullRequest
0 голосов
/ 08 октября 2019

Я работаю на стандартном шестиядерном SMP-компьютере с архитектурой x86, тактовой частотой 3,6 ГГц, в обычном коде C.

У меня есть многопоточная схема «производитель / потребитель», в которой мой поток «производителя» читает файлсо скоростью примерно 1 000 000 строк в секунду и передачей данных, которые они считывают, двум или четырем «потребительским» потокам, которые выполняют над ним небольшую работу, а затем помещают их в базу данных. Пока они потребляют, он занят чтением следующей строки.

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

Поток производителя

While(something in file)
{
   read a line
   populate 1/2 of data double buffer
   wait for consumers to idle
   set some key data
   set memory fence
   swap buffers
}

И потоки потребителя также

while(not told to die)
{
   wait for key data change event
   consume data
}

С обеих сторон закодирован цикл ожидания:

while(waiting)
{
   _mm_pause();      /* Intel say this is a good hint to processor that this is a spin wait */

    if(#iterations > 1000) yield_thread();  /* Sleep(0) on Windows, pthread_yield() on Linux */
}

Это все работает, и я получаю довольно неплохие ускорения по сравнению с эквивалентным последовательным кодом, но мой профилировщик (Intel VTune Amplifier) ​​показывает, что я трачу ужасное количество времени в своих занятых циклах ожидания,и отношение «спина» к «полезной работе» удручающе велико. Учитывая то, как профилировщик концентрирует свою обратную связь на самых загруженных разделах, это также означает, что строки кода, выполняющие полезную работу, как правило, не сообщаются, поскольку (условно говоря) их% общего возраста процессора снижается на уровне шума ... илипо крайней мере, так говорит профилировщик. Должно быть, они делают что-то в противном случае я бы не увидел ускорения!

Я могу и делаю время, но трудно различить задержки, вызванные задержкой диска в потоке производителяи задержки, затрачиваемые на синхронизацию потоков.

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

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

Любые подсказки будут действительно приветствоваться: -)

Ответы [ 2 ]

0 голосов
/ 09 октября 2019

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

Если производитель выберет «очередь заполнена», я теперь могу немедленно вернуть его поток,вместо ожидания вращения, безопасно знать, что любой потерянный фрагмент времени будет с пользой использоваться потоками потребителя. Это действительно проявляется как небольшое количество времени сна / вращения в профилировщике. Потребительские потоки также выигрывают, поскольку они имеют более равномерную рабочую нагрузку.

Чистый результат - сокращение общего времени чтения файла на 10%, а также то, что только часть файла может быть обработана за один раз. резьбовое соединение, которое предполагает, что резьбовая часть процесса происходит примерно на 15% или более быстрее.

0 голосов
/ 08 октября 2019

Даже лучше, чем быстрые блокировки не блокируется вообще. Попробуйте перейти в очередь без блокировки . Производителям и потребителям вообще не нужно ждать.

Структуры данных без блокировок безопасны для процессов, потоков и прерываний (т. Е. Один и тот же экземпляр структуры данных может безопасно использоваться одновременно и одновременно между ядрами,процессы, потоки и как внутри, так и снаружи обработчиков прерываний) никогда не спят (и поэтому безопасны для использования ядром, когда спящий режим не разрешен), работают без переключателей контекста, не могут завершиться с ошибкой (нет необходимости обрабатывать ошибки, так как их нет), выполнять и масштабировать буквально на порядки лучше, чем блокировка структур данных, а сама liblfds (начиная с версии 7.0.0) реализована так, что она не выполняет выделения (и поэтому работает с NUMA, стеком, кучей и общей памятью) и не компилируеттолько на отдельно стоящей реализации C89, но на голой реализации C89.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...