Вопросы миграции процессов

С точки зрения производительности параллельной программы пиннинг (привязка процесса к ядру процесса) очень важен.

Во первых не прикрепленный (pinned) процесс постоянно вынужден мигрировать по ядрам и даже сокетам, что приводит к частой инвалидации содержимого кэша и как следствие — увеличение количества кэшмиссов. В Numa системах это плохо еще и тем что можно «потерять» свою память.

Как правило Linux имеет некую стандартную политику распределения процессов по ядрам. Обычно она выглядит так: есть несколько процессов. Мы начинаем раскладывать с 0-го ядра в системе (см /proc/cpuinfo — нумерация ядер в системе будет аналогична), следующий процесс будет занимать другой пакет и другой сокет. Т.е. происходит неявное чередование (очевидно для размазывания загрузки по разным _физически_ процессорам).

Непосредственно для привязки процессов к CPU мы можем использовать:

1) Стандартную утилиту из поставки Linux taskset;

2) Если присутствует фреймворк OpenMP, то переменную окружения OpenMP: GOMP_CPU_AFFINITY, KMP_AFFINITY;

3) Для MPI программы привязку делает библиотека: Mvapich VIADEV_USE_AFFINITY, Intel MPI I_MPI_PIN_MODE.

Зачем может понадобиться явное указание привязки ? Чаще всего это нужно для гибридных запусков (MPI+OpenMP например). Когда MPI не подозревает о следующем уровне параллелизма (OpenMP). Для обычного mpich’a и его производных лучше просто отключить пиннинг на уровне MPI и работать через выставление KMP_AFFINITY, причем для разных ранков маска будет разной. Для Intel MPI есть более простой вариант: I_MPI_PIN_MODE=omp, а библиотека сама пересчитает сколько у вас mpi процессов и OpenMP потоков.

Кроме того, внутри одного пакета процессорные ядра имеют общий кэш. И при использовании OpenMP на этом можо сыграть. Если алгоритм написан так, что соседние потоки могут переиспользовать кэш, то лучше делать пиннинг COMPACT чем SCATTER по умолчанию. Это имеет смысл для таких алгоритмов как DGEMM, который очень локален.

dmitry

Добавить комментарий