Автоматическое распараллеливание Многоядерные/многопроцессорные Intel архитектуры Intel® Pentium® Processor Extreme Edition (2005-2007) Представил технологию двойного ядра (dual core) Intel® Xeon® Processor 5100, 5300 Series Intel® Core™2 Processor Family (2006-) Появились двухпроцессорные архитектуры. Процессоры поддерживают четыре ядра(quad-core) Intel® Xeon® Processor 5200, 5400, 7400 Series Intel® Core™2 Processor Family (2007-) Есть семейства в которых количество ядер на процессоре доведено до 6. 2-4 процессора. Intel® Atom™ Processor Family (2008-) Процессор с высокой энергоэффективностью. Intel® Core™i7 Processor Family (2008-) Hyperthreading технология. Системы с неоднородным доступом к памяти. 10/17/10 Одной из главных особенностей многоядерной архитектуры является то, что ядра совместно используют часть подсистемы памяти и шину данных. 10/17/10 Классификация многопроцессорных систем по использованию памяти 1. Массивно-параллельные компьютеры или системы с распределенной памятью. (MPP системы). Каждый процессор полностью автономен. Существует некоторая коммуникационная среда. Достоинства: хорошая масштабируемость. Недостатки: медленное межпроцедурное взаимодействие. 2. Системы с общей памятью (SMP системы) . Все процессоры равноудалены от памяти. Связь с памятью осуществляется через общую шину данных. Достоинства: хорошее межпроцессорное взаимодействие. Недостатки: плохая масштабируемость; большие затраты на синхронизацию подсистем кэшей. 3. Системы с неоднородным доступом к памяти (NUMA) . Память физически распределена между процессорами. Единое адресное пространство поддерживается на аппаратном уровне. Достоинства: Недостатки: хорошее межпроцессорное взаимодействие и масштабируемость. разное время доступа к разным сегментам памяти. 10/17/10 Intel QuickPath Architecture 10/17/10 Нестабильность работы приложений на многопроцессорных машинах с неоднородным доступом к памяти. 10/17/10 Плюсы и минусы использования многопоточных приложений. ++: Вычислительные ресурсы увеличиваются пропорционально количеству используемых реальных ядер. --: Усложнение разработки. Необходимость синхронизировать потоки. Потоки конкурируют за ресурсы. Создание потоков имеет свою цену. Вывод: В случае разработки бизнес-приложений четко осознавайте цели и цену распараллеливания вашей программы. 10/17/10 Автоматическое распараллеливание процесс автоматического преобразования последовательного программного кода в многопоточный (multi-threaded), использующий несколько ядер одновременно. Цель автоматического распараллеливания – освободить программистов от тяжелой и нудной ручной работы по разделению вычислений на различные потоки. /Qparallel enable the auto-parallelizer to generate multi-threaded code for loops that can be safely executed in parallel Выгодность автоматического распараллеливания на простом примере. REAL :: a(1000,1000),b(1000,1000),c(1000,1000) integer i,j,rep_factor DO I=1,1000 DO J=1,1000 A(J,I) = I B(J,I) = I+J C(J,I) = 0 END DO END DO DO rep_factor=1,1000 C=B/A+rep_factor END DO END 10/17/10 Хорошо и плохо масштабируемые алгоритмы void matrix_mul_matrix(int n, float C[n][n], float A[n][n], float B[n][n]) { int i,j,k; for (i=0; i<n; i++) for (j=0; j<n; j++) { C[i][j]=0; for(k=0;k<n;k++) C[i][j]+=A[i][k]*B[k][j]; } } 5 4 3 2 1 0 1 2 4 6 8 12 10/17/10 16 Плохо масштабируемые алгоритмы void matrix_add(int n, float Res[n][n],float A1[n][n], float A2[n][n], float A3[n][n],float A4[n][n], float A5[n][n], float A6[n][n], float A7[n][n], float A8[n][n]) { int i,j; for (i=0; i<n; i++) for (j=1; j<n-1; j++) Res[i][j]=A1[i][j]+A2[i][j]+A3[i][j]+A4[i][j]+ A5[i][j]+A6[i][j]+A7[i][j]+A8[i][j]+ A1[i][j+1]+A2[i][j+1]+A3[i][j+1]+A4[i][j+1]+ A5[i][j+1]+A6[i][j+1]+A7[i][j+1]+A8[i][j+1]; } 10 8 6 4 2 0 1 2 4 6 8 12 16 10/17/10 Допустимость автоматического распараллеливания Это преобразование – перестановочная оптимизация циклической конструкции. Упорядоченное выполнение итераций => неопределенный порядок выполнения итераций. Необходимое условие – отсутствие любых зависимостей внутри цикла. /Qpar-report{0|1|2|3} control the auto-parallelizer diagnostic level /Qpar-report3 сообщает причины, по которым компилятор не распараллеливает тот или иной цикл, в том числе сообщает, какие зависимости препятствуют этому. 10/17/10 Выгодность распараллеливания /Qpar_report3 информирует, если распараллеливание невыгодно. C:\test_par.c(27) (col. 1): remark: loop was not parallelized: insufficient computational work. Точное определение выгодности таких преобразований во время компиляции - достаточно тяжелая задача. Существуют эффекты производительности, которые сложно оценить, например «эффект первого прикосновения». В большинстве случаев компилятор может не иметь представления о количестве итераций в цикле. Используйте директивы распараллеливания при экспериментах с производительностью. 10/17/10 #pragma concurrent – игнорировать предполагаемые зависимости в следующем цикле. #pragma concurrent call – вызов функции в следующем цикле безопасен для параллельного выполнения. #pragma concurrentize – параллелизовать следующий цикл. #pragma no concurrentize - не параллелизовать следующий цикл. #pragma prefer concurrent - параллелизовать следующий цикл, если это безопасно. #pragma prefer serial – предложить компилятору не параллелизовать следующий цикл. #pragma serial – заставить компилятор параллелизовать следующий цикл. Автоматическое распараллеливание осуществляется использованием интерфейса OpenMP. OpenMP (Open Multi-Processing) – это программный интерфейс, который поддерживает многоплатформенное многопроцессорное программирование с общей памятью на C/C++ и Фортране на многих архитектурах. Количество потоков, используемых вашим приложением, может изменяться с помощью установки переменной окружения OMP_NUM_THREADS (по умолчанию будут использоваться все доступные ядра). 8 Threads 16 Threads 10/17/10 Распараллеливание цикла выглядит как создание функции, в которую в качестве аргументов передаются границы цикла и все используемые объекты. Внутри функции находится исходный цикл, но его границы определяются при помощи параметров функции. Запускаются несколько экземпляров данной функции в разных потоках с различными значениями границ цикла. Т.е. итерационное пространство цикла разбивается на несколько частей, и каждый поток обрабатывает свою часть итерационного пространства. /Qpar-runtime-control[n] Control parallelizer to generate runtime check code for effective automatic parallelization. n=0 no runtime check based auto-parallelization n=1 generate runtime check code under conservative mode (DEFAULT when enabled) n=2 generate runtime check code under heuristic mode n=3 generate runtime check code under aggressive mode 10/17/10 Взаимодействие с другими оптимизациями циклических конструкций • Объединение циклов и создание больших циклов. • Автоматическое распараллеливание. • Оптимизация цикла в поточной функции в соответствии с обычными соображениями (развертка, векторизация, разбиение на несколько циклов и т.п.). Эти соображения можно использовать при написании программы. Стремитесь создавать большие циклы без зависимостей, т.е. такие, чтобы итерации могли выполняться в произвольном порядке. 10/17/10 Предвыборка - загрузка данных из относительно медленной памяти в кэш до того, как эта память непосредственно потребовалась процессору. Существуют несколько методов использования этой техники для оптимизации приложения: • Использование встроенных функций (интриниксов); • Использование компиляторной опции. Функция предвыборки определена в xmmintrin.h и имеет форму #include <xmmintrin.h> enum _mm_hint { _MM_HINT_T0 = 3, (L1) _MM_HINT_T1 = 2, (L2) _MM_HINT_T2 = 1, (L3) _MM_HINT_NTA = 0 }; void _mm_prefetch(void *p, enum _mm_hint h); Она подгружает в кэш кэш-линию, начиная с указанного адреса (размер кэш линии 64 байта). В случае с Фортраном используется вызов CALL mm_prefetch(P,HINT). 10/17/10 Пример использования DO I=1,N,SEC JJ=16 DO J=1,K A(J,I) = A(J,I)+B(J,I)+C(J,I) #ifdef PERF IF(JJ==16) THEN CALL mm_prefetch(A(J,I+SEC),1) CALL mm_prefetch(B(J,I+SEC),1) CALL mm_prefetch(C(J,I+SEC),1) JJ = 0 ENDIF #endif JJ=JJ+1 END DO END DO 10/17/10 Программная предвыборка может быть полезна для решения проблем типичного C++ кода, одной из проблем которого является проблема доступа к памяти, возникающая при обработке различных массивных списков. Если вы последовательно обрабатываете список каких-то объектов, имеет смысл параллельно подгружать в кэш следующий объект и те объекты, к которым обрабатывающая функция обращается при помощи указателей полей данного объекта. 10/17/10 Использование ключей компилятора /Qopt-prefetch[:n] enable levels of prefetch insertion, where 0 disables. n may be 0 through 4 inclusive. Default is 2. /Qopt-prefetchdisable(DEFAULT) prefetch insertion. Equivalent to /Qopt-prefetch:0 10/17/10 CEAN (C/C++ Extensions for Array Notations Programming Model) Описание массивов Length Fixed Variable (C99) Storage Class Static Declaration static int a[16][128] Auto void foo(void) { int a[16][128]; } Parameter void bar(int a[16][128]); Heap int (*p2d)[128]; Auto void foo(int m, int n) { int a[m][n]; } Parameter void bar(int m, int n, int a[m][n]); Heap void bar(int m, int n) { int (*p2d)[n]; } 10/17/10 Описание областей section_operator :: = [<lower bound>:<length>:<stride> a[0:3][0:4] b[0:2:3] Вы должны использовать расширение –std=c99 (Linux и MAC OS) или /Qstd=c99 Пример декларации массива и использования секции: typedef int (*p2d)[128]; p2d p = (p2d) malloc (sizeof(int)*rows*128); p[0:rows][:] Большинство C/C++ операторов доступны для работы с секциями. a[:]*b[:] // поэлементное умножение a[3:2][2:2] + b[5:2][5:2] // сложение матриц a[0:4]+c // добавление скаляра к вектору a[:][:] = b[:][1][:] + c // матричное присвоение 10/17/10 Прототипы некоторых матричных функций Function Prototypes __sec_reduce(fun, identity, a[:]) Descriptions Generic reduction function. Reduces fun across the array a[:] using identity as the initial value. __sec_reduce_add(a[:]) Built-in reduction function. Adds values passed as arrays __sec_reduce_mul(a[:]) Built-in reduction function. Multiplies values passed as arrays __sec_reduce_all_zero(a[:]) Built-in reduction function. Tests that array elements are all zero Built-in reduction function. Tests that array elements are all non-zero Built-in reduction function. Tests for any array element that is non-zero Built-in reduction function. Determines the minimum value of array elements Built-in reduction function. Determines the maximum value of array elements Built-in reduction function. Determines the index of minimum value of array elements Built-in reduction function. Determines the index of maximum value of array elements __sec_reduce_all_nonzero(a[:]) __sec_reduce_any_nonzero(a[:]) __sec_reduce_min(a[:]) __sec_reduce_max(a[:]) __sec_reduce_min_ind(a[:]) __sec_reduce_max_ind(a[:]) 10/17/10 #include <stdio.h> #include <stdlib.h> #define N 2000 typedef double (*p2d)[]; void matrix_mul(int n, double a[n][n], int main() { p2d a= (p2d)malloc(N*N*sizeof(double)) ; p2d b= (p2d)malloc(N*N*sizeof(double)) ; p2d c= (p2d)malloc(N*N*sizeof(double)); double b[n][n],double c[n][n]) { matrix_mul(N,a,a,a); matrix_mul(N,a,b,c); int i,j; a[:][:] =1; free(a); free(b); free(c); b[:][:] =-1; for(i=0;i<n;i++) } for(j=0;j<n;j++) c[i][j]=c[i][j]+ __sec_reduce_add(a[i][:]*b[:][j]); return; } 10/17/10 Спасибо за внимание! 10/17/10