Какие ограничения масштабируются в этой простой программе OpenMP?

Я пытаюсь понять ограничения на параллелизацию в 48-ядерной системе (4xAMD Opteron 6348, 2.8 Ghz, 12 ядер на процессор). Я написал этот крошечный код OpenMP, чтобы проверить ускорение в том, что, по моему мнению, будет наилучшей возможной ситуацией (задача неловко параллельна):

// Compile with: gcc scaling.c -std=c99 -fopenmp -O3 #include  #include  int main(){ const uint64_t umin=1; const uint64_t umax=10000000000LL; double sum=0.; #pragma omp parallel for reduction(+:sum) for(uint64_t u=umin; u<umax; u++) sum+=1./u/u; printf("%e\n", sum); } 

Я с удивлением обнаружил, что масштабирование сильно нелинейно. Для запуска кода с 48 streamами требуется 3,9 с 3,1 с 36 streamами, 3,7 с 24 streamами, 4,9 с 12 streamами и 57 секунд для кода для работы с 1 streamом.

К сожалению, я должен сказать, что на компьютере есть один процесс, использующий 100% одного ядра, поэтому это может повлиять на него. Это не мой процесс, поэтому я не могу закончить его, чтобы проверить разницу, но почему-то я сомневаюсь, что это делает разницу между ускорением 19 ~ 20x и идеальным 48-кратным ускорением.

Чтобы убедиться, что это не проблема OpenMP, я одновременно запускал две копии программы с 24 streamами (один с umin = 1, umax = 5000000000, а другой с umin = 5000000000, umax = 10000000000). В этом случае обе копии программы заканчиваются после 2.9s, так что это точно так же, как запуск 48 streamов с одним экземпляром программы.

Что мешает линейному масштабированию с помощью этой простой программы?

Я не уверен, что это квалифицируется как ответ, но это похоже на комментарий, поэтому мы идем.

Я никогда не замечал особенно линейной производительности против количества streamов в любом из моих проектов. Во-первых, мне кажется, что есть планировщик, который является чем угодно, но строго справедливым. OpenMP, возможно, делит задачу равномерно среди своей группы streamов с самого начала, а затем объединяет их. На каждом ящике Linux, с которым я получил удовольствие, я ожидал бы, что несколько streamов закончатся рано, и несколько streamов для отставания. Другие платформы будут отличаться. Однако это работает, конечно, вы ждёте, чтобы медленнее догнать. Стохастически говоря, есть импульс обработки резьбы, проходящий через что-то вроде колокольчика, тем больше streamов, чем я должен думать, и вы никогда не делаете, пока задний край не пересечет финишную черту.

Что говорит top ? Сообщает ли вам, что ваш процесс получает 2000% процессор на 20 streamов, 4000% на 40? Бьюсь об заклад, он сужается. htop , кстати, обычно показывает общее количество процессов и отдельные строки для каждого streamа. Это может быть интересно смотреть.

С такой крошечной петлей вы, вероятно, не сталкиваетесь с трэшем в кеш или каким-либо таким раздражением. Но еще одна проблема, связанная с бритьем сверху: как и любой современный многоядерный процессор, Opteron работает с более высокой тактовой частотой, когда это круто. Чем больше вы нагреете сердечники, тем меньше вы увидите турбо-режим.

У меня есть два важных момента: два, почему ваши результаты не являются линейными. Первый из них касается модhive с гиперstreamом Intel и AMD. Следующий – о турбочастотных режимах с Intel и AMD

1.) Гиперstreamи и модули AMD / ядра

Слишком много людей путают streamи Intel Hyper и ядра AMD в модулях как реальные ядра и ожидают линейной скорости. Процессор Intel с гиперstreamом может работать в два раза больше гиперstreamов / аппаратных streamов в качестве ядер. У AMD также есть собственная технология, где фундаментальная единица называется модулем, и каждый модуль имеет то, что AMD бесхитростно называет kernelм. Что такое модуль, что такое kernel . Одной из причин, почему это легко смущает, является то, что, например, с помощью Task Mangager в windowsх с гипертекстом он показывает количество аппаратных streamов, но он говорит о процессорах. Это вводит в заблуждение, поскольку это не количество ядер процессора.

У меня недостаточно знаний о AMD, чтобы вдаваться в детали, но насколько я понимаю, у каждого модуля есть одна единица с плавающей запятой (но две целые единицы). Поэтому вы не можете ожидать линейной скорости выше числа ядер Intel или модhive AMD для операций с плавающей запятой.

В вашем случае Opteron 6348 имеет 2 матрицы на каждый процессор, каждый из которых имеет 3 модуля, каждый из которых имеет 2 “ядра”. Хотя это дает 12 ядер, на самом деле есть только 6 единиц с плавающей точкой.

Я запустил ваш код на одном гнезде Intel Xeon E5-1620 @ 3.6 ГГц. У этого есть 4 ядра и гиперпоточность (так восемь аппаратных streamов). Я получил:

 1 threads: 156s 4 threads: 37s (156/4 = 39s) 8 threads: 30s (156/8 = 19.5s) 

Обратите внимание, что для 4 streamов масштабирование является почти линейным, но для 8 streamов гиперпоточность помогает лишь немного (по крайней мере, это помогает). Еще одно странное замечание заключается в том, что мои однопоточные результаты намного ниже вашего (режим MSBC2013 с 64-разрядной версией). Я бы ожидал, что более быстрое однопоточное kernel ​​моста из плюща легко превзойдет более медленное kernel ​​драйвера AMD. Для меня это не имеет смысла.

2.) Intel Turbo Boost и AMD Turbo Core.

Intel имеет технологию Turbo Boost, которая изменяет тактовую частоту в зависимости от количества запущенных streamов. Когда все streamи запускаются, турбоподдержка имеет наименьшее значение. В Linux единственное приложение, которое я знаю, которое может измерить это при выполнении операции, – это powertop. Получение реальной рабочей частоты не так просто измерить (для этого нужен root-доступ). В Windows вы можете использовать CPUz. В любом случае результат заключается в том, что вы не можете ожидать линейного масштабирования при выполнении только одного streamа по сравнению с максимальным количеством реальных ядер.

Еще раз, у меня мало опыта работы с процессорами AMD, но насколько я могу сказать, их технология называется Turbo Core, и я ожидаю, что эффект будет подобным. Именно по этой причине хороший сравнительный тест отключает режимы турбочастот (в BIOS, если можно) при сравнении многопоточного кода.

Наконец, я получил возможность сравнить код с полностью разгруженной системой: введите описание изображения здесь

Для динамического расписания я использовал schedule(dynamic,1000000) . Для статического графика я использовал значение по умолчанию (равномерно между ядрами). Для привязки резьбы я использовал export GOMP_CPU_AFFINITY="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47" .

Основная причина сильно нелинейного масштабирования для этого кода заключается в том, что то, что AMD называет «ядрами», на самом деле не являются независимыми ядрами. Это было частью (1) ответа redrum. Это ясно видно на графике выше от внезапного плато ускорения на 24 нитях; это действительно очевидно при динамическом планировании. Это также очевидно из привязки нити, которую я выбрал: получается, что я написал выше, было бы ужасным выбором для привязки, потому что в итоге вы получаете два streamа в каждом «модуле».

Второе по величине замедление происходит от статического планирования с большим количеством streamов. Неизбежно возникает дисбаланс между самыми медленными и быстрыми streamами, что приводит к большим колебаниям в времени выполнения, когда итерации разделены на большие куски со статическим планированием по умолчанию. Эта часть ответа была получена как от комментариев Христо, так и от ответа Солта.

Я не знаю, почему эффекты «Turbo Boost» не более выражены (часть 2 ответа Redrum). Кроме того, я не на 100% уверен, что (по-видимому, накладные расходы) последний бит масштабирования приходит, теряется (мы получаем 22x производительность вместо ожидаемых 24x от линейного масштабирования в количестве модhive ). Но в остальном вопрос довольно хорошо ответил.