Ответ 1
Люди из HPC обычно говорят, что одного потока обычно недостаточно для насыщения одной ссылки на память, что обычно верно для сетевых ссылок. Здесь - это быстрый и грязный memsetter с поддержкой OpenMP, который я написал для вас, который заполняет нулями дважды 2 гигабайта памяти. И вот результаты с использованием GCC 4.7 с различным количеством потоков на разных архитектурах (максимальные значения из нескольких запущенных отчетов):
GCC 4.7, код, скомпилированный с помощью -O3 -mtune=native -fopenmp
:
Четырехъядерный Intel Xeon X7350 - четырехъядерный процессор pre-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
(1-е касание выполняется медленно, так как команда нитей создается с нуля, а операционная система отображает физические страницы в виртуальное адресное пространство, зарезервированное malloc(3)
)
Один поток уже насыщает полосу пропускания памяти на одном канале CPU ↔ 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 node, где выполняется поток, который первым коснется виртуальный адрес на определенной странице. Фиксирование нити (привязка к конкретным ядрам ЦП) имеет важное значение для таких систем, поскольку миграция потоков приводит к удаленному доступу, который медленнее. Поддержка pinnig доступна в большинстве случаев работы OpenMP. GCC с его libgomp
имеет переменную окружения GOMP_CPU_AFFINITY
, у Intel есть переменная среды KMP_AFFINITY
и т.д. Также OpenMP 4.0 представил концептуальную концепцию местоположений, не зависящую от поставщика.
Изменить:. Для полноты здесь приведены результаты запуска кода с массивом 1 GiB на MacBook Air с Intel Core i5-2557M (двухъядерный Sandy Bridge CPU с 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 байта и, возможно, насытить ссылку на память.
Важное сообщение здесь состоит в том, что иногда векторизация и многопоточность не являются ортогональными для ускорения операции.