Синхронизация потоков для очень коротких задач - PullRequest
0 голосов
/ 26 марта 2020

У меня есть приложение C ++, работающее на winapi. Портативность не проблема. Все, что я хочу, это максимальная производительность. У меня есть базовое c понимание проблем многопоточности и синхронизации, но ограниченный опыт работы с множеством вариантов, от winapi через потоки C ++ до сторонних библиотек.

В критическом ядре производительности моего приложения я определил все oop, который можно распараллелить. Мне удалось разделить l oop на 4 части, которые не зависят друг от друга. Я хотел бы делегировать работу 4 потокам, работающим параллельно. Основной поток должен дождаться, пока все 4 потока выполнят свою работу, прежде чем он продолжится.

Звучит очень просто. Однако в настоящее время l oop занимает всего около 10 микросекунд при работе на одном потоке. Боюсь, что методы синхронизации, которые вызывают переключение в ядро ​​(события, мьютексы и т. Д. c.), Вызовут больше издержек, чем может сэкономить распараллеливание. SRWLocks + условные переменные утверждают, что они очень легковесны, но я не нашел способа решить мою синхронизацию с этими инструментами.

Конечно, я мог бы протестировать все виды API-интерфейсов синхронизации, но я уверен, что это было сделано раньше.

Поэтому мой вопрос: есть ли разумный способ синхронизации очень коротких задач и если да, то каковы соответствующие инструменты?

Ответы [ 2 ]

1 голос
/ 26 марта 2020

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

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

0 голосов
/ 27 марта 2020

Алгоритм обновления состоит из двух этапов. Каждый из этих шагов может быть применен к узлам в произвольном порядке, но шаг 1 должен быть завершен, прежде чем шаг 2 может начаться. Я могу разделить весь net на четыре (или более) части и делегировать каждую часть в отдельный поток. Моя проблема: каждый поток должен сделать паузу после шага 1 и подождать, пока все потоки не завершат свою работу. Затем каждый поток выполняет шаг 2, ожидает завершения других потоков и т. Д.

Вы хотите разбить работу на большое количество маленьких кусков и иметь фиксированный пул потоков, которые принимают куски по Работа. Не делайте 8 потоков на 8-ядерном компьютере и не делите работу на 8 частей. Этот алгоритм будет работать плохо, если по тем или иным причинам только 7 из этих ядер будут работать за вас. Вашему алгоритму потребуется вдвое больше времени, чем во второй половине времени, когда работает только одно ядро.

Самый простой способ - создать дополнительный поток отгрузки. Просто держите счет «рабочих единиц» где-нибудь под защитой мьютекса. Когда поток завершает рабочую единицу, уменьшите счет «рабочей единицы». Когда оно достигнет нуля, передайте переменную условия. Это разбудит поток диспетчеризации, который затем сделает все возможное для возобновления рабочих потоков. Он может запустить их, установив счетчик «рабочий блок» на правильный уровень и передав другую переменную условия, которую ожидают рабочие потоки.

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

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

...