Ответ 1
Вам не хватает понятия о том, каково влияние использования динамического планирования на служебные данные OpenMP.
Динамическое планирование должно использоваться, чтобы помочь нам в проблемах с балансировкой нагрузки, где каждая итерация цикла может занимать разные промежутки времени, а статическое распределение итераций скорее всего создаст дисбаланс работы между различными потоками. Дисбаланс работы приводит к расходованию процессорного времени, поскольку потоки, которые заканчиваются раньше, просто ждут окончания остальных потоков. Динамическое планирование преодолевает это, распределяя куски цикла в порядке поступления. Но это добавляет накладные расходы, так как система времени выполнения OpenMP должна внедрять бухгалтерию, на которой была выведена итерация, а какая нет и должна выполнять некоторый тип синхронизации. Кроме того, каждый поток должен сделать хотя бы один вызов OpenMP runtime каждый раз, когда он заканчивает свой блок итераций и ищет другой. При статичном планировании все итерационные блоки предварительно вычисляются заранее, а затем каждый поток проходит по своей части без какого-либо взаимодействия с средой выполнения OpenMP.
Наиболее важным различием между статическим и динамическим планированием является размер итерационного куска (т.е. количество последовательных итераций цикла, которые каждый поток выполняет, прежде чем искать работу в другой части итерационного пространства). Если этот параметр опущен, размер блока с статическим планированием по умолчанию равен #_of_iterations/#_of_threads
, а размер по умолчанию для динамического планирования - 1
, то есть каждый поток должен запрашивать время выполнения OpenMP для каждой итерации распределенного цикла.
Что происходит в вашем случае, так это то, что без collapse(3)
у вас есть NNN
итерационные фрагменты внешнего цикла, и каждый поток выполняет итерации NNN*NNN
(внутренних циклов), прежде чем запрашивать время выполнения OpenMP для другой итерации. Когда вы свертываете петли, количество итерационных кусков увеличивается до NNN*NNN*NNN
, т.е. Есть много больше кусков, и каждый поток будет запрашивать время выполнения OpenMP для куска после каждой итерации.
Это создает еще одну проблему, когда внутренние контуры рушится с самой внешней: произойдет, что многие потоки получат итерации, которые имеют одинаковое значение i
, что нарушит вычисление, так как порядок выполнения не гарантируется, и это может произойти, что последний поток, который записывается в res1[i]
, не тот, который выполняет последнюю итерацию обоих внутренних циклов.