Свертывание циклов требует, чтобы вы знали, что вы делаете, так как это может привести к очень недружественному кешированию пространства итерации или ввести зависимости данных в зависимости от того, как произведение значений l oop связано с количеством потоков.
Представьте себе следующий построенный пример, который на самом деле не так уж редок (число l oop мало для иллюстрации точки):
for (int i = 0; i < 7; i++)
for (int j = 0; j < 3; j++)
a[i] += b[i][j];
Если вы распараллелите внешний l oop, три потока получают две итерации и один поток получает только одну, но все они выполняют все итерации внутренней l oop:
---0-- ---1-- ---2-- -3- (thread number)
000111 222333 444555 666 (values of i)
012012 012012 012012 012 (values of j)
Каждая a[i]
обрабатывается только одним потоком , Умные компиляторы могут реализовать внутренний l oop, используя оптимизацию регистра, сначала накапливая значения в регистре и назначая только a[i]
в самом конце, и он будет работать очень быстро.
Если вы свернете две петли, вы попадаете в совершенно другую ситуацию. Поскольку в настоящее время общее число итераций составляет 7x3 = 21, разделение по умолчанию будет (в зависимости от компилятора и среды выполнения OpenMP, но большинство из них делает это) пять итераций на поток, а одна получает шесть итераций:
--0-- --1-- --2-- ---3-- (thread number)
00011 12223 33444 555666 (values of i)
01201 20120 12012 012012 (values of j)
Как видите, теперь a[1]
обрабатывается как потоком 0, так и потоком 1. Аналогично, a[3]
обрабатывается как потоком 1, так и потоком 2. И у вас это есть - вы только что ввели зависимость данных, которая не было в предыдущем случае, так что теперь вы должны использовать atomic
, чтобы предотвратить скачки данных. Цена, которую вы платите за синхронизацию, намного выше, чем выполнение одной итерации более или менее! В вашем случае, если вы свернете только два внешних цикла, вам вообще не нужно будет использовать atomic
(хотя, в вашем конкретном случае, 4 делит 100, и даже при сворачивании всех циклов вам не нужно atomic
, но вам это нужно в общем случае).
Другая проблема заключается в том, что после свертывания циклов существует один индекс l oop и оба индекса i
и j
имеют быть восстановленным из этого нового индекса с использованием операций деления и по модулю. Для простых тел l oop, подобных вашему, накладные расходы на восстановление индексов могут быть просто слишком высокими.