Мое предположение номер один, хотя уже очень поздно (извините за оживление старого потока, это интересно!) - поиск однородных циклов преобразования , где каждая итерация цикла изменяет совершенно независимый фрагмент данных из другие итерации.
Вместо того, чтобы думать о том, как превратить эту старую кодовую базу в асинхронную, выполняющую все виды операций параллельно (что может вызывать все виды проблем из-за худшей, чем однопоточная производительность, из-за плохих шаблонов блокировки или экспоненциально хуже, гонки условия / взаимоблокировки, пытаясь сделать это, оглядываясь назад, на код, который вы не можете полностью понять), пока придерживайтесь последовательного мышления для всего проекта приложения, но выявляйте или извлекайте простые однородные циклы преобразования. Не переходите от навязчивой многопоточности на уровне дизайна, а затем попытайтесь углубиться в детали. Сначала начните с ненавязчивой многопоточности с тонкими деталями реализации и конкретными точками доступа.
То, что я имею в виду под однородными циклами, в основном представляет собой преобразование данных очень простым способом, например:
for each pixel in image:
make it brighter
Это очень просто рассуждать, и вы можете безопасно распараллелить этот цикл без каких-либо проблем, используя OMP или TBB или что-либо еще, и не запутавшись в синхронизации потоков. Достаточно одного взгляда на этот код, чтобы полностью понять его побочные эффекты.
Постарайтесь найти как можно больше горячих точек, подходящих для этого типа простого однородного цикла преобразования, и если у вас есть сложные циклы, которые обновляют множество различных типов данных со сложными потоками управления, которые вызывают сложные побочные эффекты, то попытайтесь рефакторинг в сторону этих однородные петли. Часто сложный цикл, который вызывает 3 разнородных побочных эффекта для 3 различных типов данных, можно превратить в 3 простых однородных цикла, каждый из которых запускает только один вид побочного эффекта для одного типа данных с более простым потоком управления. Выполнение нескольких циклов вместо одного может показаться немного расточительным, но циклы становятся проще, однородность часто приводит к более дружественным к кэшу шаблонам последовательного доступа к памяти по сравнению со спорадическими шаблонами произвольного доступа, и тогда вы склонны находить гораздо больше возможностей для безопасно распараллелить (а также векторизовать) код простым способом.
Сначала вы должны тщательно понять побочные эффекты любого кода, который вы пытаетесь распараллелить (и я имею в виду полностью !!! ), поэтому поиск этих однородных циклов дает вам изолированную области кодовой базы, которые вы можете легко рассуждать с точки зрения побочных эффектов, до точки, где вы можете уверенно и безопасно распараллеливать эти горячие точки. Это также улучшит удобство сопровождения кода, упрощая рассуждения об изменениях состояния, происходящих в этом конкретном фрагменте кода. Сохраните мечту о многопоточном приложении uber, которое будет выполнять все параллельно на будущее. На данный момент, сосредоточиться на выявлении / извлечении критических по производительности, однородных циклов с простыми потоками управления и простыми побочными эффектами. Это ваши приоритетные цели для распараллеливания с простыми распараллеленными циклами.
Теперь, по общему признанию, я несколько уклонился от ваших вопросов, но большинству из них не нужно обращаться, если вы делаете то, что я предлагаю, по крайней мере, пока вы не добились своего выхода в точку, где вы больше задумываетесь о многопоточности проекты в отличие от простого распараллеливания деталей реализации. И вам даже не нужно заходить так далеко, чтобы иметь очень конкурентоспособный продукт с точки зрения производительности. Если вы выполняете сложную работу в одном цикле, вы можете выделить аппаратные ресурсы для ускорения этого цикла вместо одновременного выполнения множества операций. Если вам нужно прибегнуть к большему количеству асинхронных методов, например, если ваши горячие точки больше связаны с вводом / выводом, ищите подход асинхронизации / ожидания, при котором вы запускаете асинхронную задачу, но в то же время выполняете некоторые действия, а затем ждете выполнения асинхронной задачи. завершить. Даже если это не является абсолютно необходимым, идея состоит в том, чтобы разделить отдельные области вашей кодовой базы, где вы можете со 100% уверенностью (или, по крайней мере, 99,999999,9%) сказать, что многопоточный код является правильным.
Вы никогда не хотите играть с условиями гонки. Нет ничего более деморализующего, чем обнаружение какого-то непонятного состояния гонки, которое возникает только один раз в полнолуние на компьютере какого-то случайного пользователя, в то время как вся ваша команда QA не может воспроизвести его, только через 3 месяца столкнется с ним самостоятельно, за исключением одного раза вы выполнили сборку релиза без доступной отладочной информации, в то время как вы бросали и включали сон, зная, что ваша кодовая база может вылететь в любой момент, но таким образом, что никто никогда не сможет последовательно воспроизвести. Поэтому будьте спокойны с многопоточными унаследованными кодовыми базами, по крайней мере, на данный момент, и придерживайтесь многопоточных изолированных, но критических разделов кодовой базы, в которых побочные эффекты очень просты для размышления. И протестируйте все это - в идеале примените подход TDD, когда вы пишете тест для кода, который вы собираетесь использовать в многопоточности, чтобы убедиться, что он дает правильный вывод после того, как вы закончите ... хотя условия гонки - это такие вещи, которые легко пролететь под радаром модульного и интеграционного тестирования, поэтому вам снова необходимо уметь понять все побочные эффекты, которые возникают в данном фрагменте кода, прежде чем пытаться выполнить его в многопоточном режиме. Лучший способ сделать это - максимально облегчить понимание побочных эффектов с помощью простейших потоков управления, вызывающих только один тип побочных эффектов для всего цикла.