В параллельном коде OpenMP, будет ли какая-то польза для параллельного запуска memset?
У меня есть блоки памяти, которые могут быть довольно большими (больше, чем кэш L2), и иногда я должен установить их на ноль. memset хорош в последовательном коде, но как насчет параллельного кода? Есть ли у кого-нибудь опыт, если вызов memset из параллельных потоков действительно ускоряет работу больших массивов? Или даже используя простой openmp параллель для циклов?
2 ответа
Люди в HPC обычно говорят, что одного потока обычно недостаточно для насыщения одной ссылки памяти, то же самое обычно справедливо и для сетевых ссылок. Вот быстрый и грязный мемсеттер с поддержкой OpenMP, который я написал для вас, который заполняет нулями в два гигабайта памяти. А вот результаты с использованием GCC 4.7 с разным количеством потоков на разных архитектурах (сообщается о максимальных значениях из нескольких запусков):
GCC 4.7, код скомпилирован с -O3 -mtune=native -fopenmp
:
Четырехъядерный процессор Intel Xeon X7350 - четырехядерный процессор до Nehalem с отдельным контроллером памяти и лицевой панелью
один разъем
threads 1st touch rewrite
1 1452.223 MB/s 3279.745 MB/s
2 1541.130 MB/s 3227.216 MB/s
3 1502.889 MB/s 3215.992 MB/s
4 1468.931 MB/s 3201.481 MB/s
(Первое касание происходит медленно, поскольку команда потоков создается с нуля, а операционная система отображает физические страницы в виртуальное адресное пространство, зарезервированное malloc(3)
)
Один поток уже насыщает пропускную способность памяти одной ссылки ЦП <-> NB. (NB = Северный мост)
1 поток на сокет
threads 1st touch rewrite
1 1455.603 MB/s 3273.959 MB/s
2 2824.883 MB/s 5346.416 MB/s
3 3979.515 MB/s 5301.140 MB/s
4 4128.784 MB/s 5296.082 MB/s
Два потока необходимы для насыщения полной пропускной способности памяти ссылки памяти NB <->.
Octo-Socket Intel Xeon X7550 - 8- канальная система NUMA с восьмиъядерными процессорами (CMT отключена)
один разъем
threads 1st touch rewrite
1 1469.897 MB/s 3435.087 MB/s
2 2801.953 MB/s 6527.076 MB/s
3 3805.691 MB/s 9297.412 MB/s
4 4647.067 MB/s 10816.266 MB/s
5 5159.968 MB/s 11220.991 MB/s
6 5330.690 MB/s 11227.760 MB/s
По крайней мере, 5 потоков необходимы для насыщения полосы пропускания одного канала памяти.
1 поток на сокет
threads 1st touch rewrite
1 1460.012 MB/s 3436.950 MB/s
2 2928.678 MB/s 6866.857 MB/s
3 4408.359 MB/s 10301.129 MB/s
4 5859.548 MB/s 13712.755 MB/s
5 7276.209 MB/s 16940.793 MB/s
6 8760.900 MB/s 20252.937 MB/s
Пропускная способность масштабируется почти линейно с количеством потоков. Основываясь на наблюдениях за одним сокетом, можно сказать, что по крайней мере 40 потоков, распределенных как 5 потоков на сокет, были бы необходимы для насыщения всех восьми ссылок памяти.
Основной проблемой в системах NUMA является политика памяти первого касания - память выделяется на узле NUMA, где выполняется поток, который первым касается виртуального адреса на определенной странице. Закрепление потоков (привязка к конкретным ядрам процессора) имеет важное значение в таких системах, поскольку миграция потоков приводит к удаленному доступу, который медленнее. Поддерживается для pinnig доступно в большинстве сред выполнения OpenMP. GCC со своим libgomp
имеет GOMP_CPU_AFFINITY
переменная среды, Intel имеет KMP_AFFINITY
переменная окружения и т. д. Кроме того, OpenMP 4.0 представил независимую от производителя концепцию мест.
Изменить: Для полноты, вот результаты запуска кода с массивом 1 ГБ на MacBook Air с Intel Core i5-2557M (двухъядерный процессор Sandy Bridge с HT и QPI). Компилятор GCC 4.2.1 (сборка Apple LLVM)
threads 1st touch rewrite
1 2257.699 MB/s 7659.678 MB/s
2 3282.500 MB/s 8157.528 MB/s
3 4109.371 MB/s 8157.335 MB/s
4 4591.780 MB/s 8141.439 MB/s
Почему такая высокая скорость даже с одной нитью? Небольшое исследование с gdb
показывает, что memset(buf, 0, len)
переводится компилятором OS X в bzero(buf, len)
и что векторизованная версия с поддержкой SSE4.2 называется bzero$VARIANT$sse42
обеспечивается libc.dylib
и используется во время выполнения. Он использует MOVDQA
Инструкция обнулить 16 байт памяти сразу. Вот почему даже с одним потоком пропускная способность памяти почти насыщена. Однопоточная версия с поддержкой AVX с использованием VMOVDQA
может обнулить 32 байта за раз и, вероятно, насытить ссылку памяти.
Важное сообщение здесь заключается в том, что иногда векторизация и многопоточность не являются ортогональными для ускорения работы.
Ну, всегда есть кэш L3...
Однако весьма вероятно, что это будет связано с пропускной способностью основной памяти; добавление большего параллелизма вряд ли улучшит ситуацию.