| КЛАСТЕР | ![]() ![]() |
Рассмотрим задачу умножения 2 квадратных матриц 1000x1000.
Программа компилировалась с 5 различными флагами оптимизации:
Полученные результаты:
| flags | -O0 | -O1 | -O2 | -O3 | -fast |
|---|---|---|---|---|---|
| Умножение матриц (сек.) | 8.11 | 2.76 | 2.22 | 2.29 | 2.12 |
Использование базовой оптимизации в данном случае дает выигрыш по времени в 3.65 раза, что очень существенно. Однако, более агрессивная оптимизиция в этом же случае не дает выигрыша (-О3 и -fast).
Рассмотрим эту задачу на лучшем, по результатам первого запуска, ключе -fast с различными вариантами перестановок циклов. При этом память для матриц пусть выделяется по строкам:
double **a=(double**)malloc(sizeof(double*)*N); for (i=0;i<N;i++) a[i]=(double*)malloc(sizeof(double)*N);
Возможны 6 различных вариантов организации тройного цикла при перемножении матриц:
Пример цикла перемножения:
for (i=0;i<N;i++) for (j=0;j<N;j++) for (k=0;k<N;k++) testee[i][j] += A[i][k]+B[k][j];
Во всех 6 случаях решение задачи дает один и тот же результат, но совершенно разную производительность.
| -fast | ijk | ikj | jik | jki | kij | kji |
|---|---|---|---|---|---|---|
| Умножение матриц (сек.) | 7.15 | 2.16 | 5.35 | 12.48 | 2.64 | 12.28 |
Ннаилучшую производительность показывает выбор варианта ikj, в случае выделения памяти под матрицы по строкам. Рассмотрим почему так получается:
for (i=0;i<N;i++) for (k=0;k<N;k++) for (j=0;j<N;j++) testee[i][j] += A[i][k]+B[k][j];
В случае ikj, в самом внутреннем цикле по j и по матрице B, и по результирующей матрице идет обход элементов в цикле по строке (а так как память выделяется по строкам, то обращение к соседнему элементу по строке происходит намного быстрее, чем обращение к соседнему элементу по столбцу). Логично предположить, что наиболее быстро будет выполняться перемножение матриц с организацией внутреннего цикла по j. Так и есть, лучшие варианты это ikj и kij. Ну а ikj оказался быстрее чем kij, так как во втором цикле по вложенности, в случае выбора цикла по k, пробегается матрица А по строке и матрица В по столбцу. В случае выбора цикла по I, пробегается две матрицы по столбцам (A и testee).
Таким образом, в данном примере, выбор грамотной организации вычислений, используя знания о размещении данных в памяти, дает выигрыш в 6 раз по сравнению с наихудшим выбором организации вычислений.
При решении задачи только сочетание правильного использования флагов оптимизации и правильной организации вычислений дает оптимальный результат.
Рассмотрим другую задачу, решение уравнения Пуассона, с теми же самыми флагами:
| flags | -O0 | -O1 | -O2 | -O3 | -fast |
|---|---|---|---|---|---|
| Решение уравнения Пуассона (сек.) | 19.98 | 9.04 | 6.99 | 6.9 | 6.51 |
Здесь применение более агрессивной оптимизации дает выигрыш по времени порядка 7%.
Использование опций оптимизации компилятора дает существенный выигрыш в производительности, но не всегда более агрессивная оптимизация даст лучший результат. Во многих случаях достаточно базовой оптимизации -O2. Для компилирования программ на кластере СКИФ МГУ для начала следует попробовать опцию -O2, а уже затем пытаться использовать более агрессивную оптимизацию. Для получения базовой информации об оптимизации по компилятору Intel можно набрать icc -help opt в коммандной строке при входе на кластер. Для получения расширенной информации об опциях оптимизации можно набрать icc -help advanced либо обратиться к документации.