Я запускаю простое ядро, которое добавляет два потока комплексных значений двойной точности. Я распараллелил это с помощью OpenMP с пользовательским планированием: контейнер slice_indices
содержит разные индексы для разных потоков.
for (const auto& index : slice_indices)
{
auto* tens1_data_stream = tens1.get_slice_data(index);
const auto* tens2_data_stream = tens2.get_slice_data(index);
#pragma omp simd safelen(8)
for (auto d_index = std::size_t{}; d_index < tens1.get_slice_size(); ++d_index)
{
tens1_data_stream[d_index].real += tens2_data_stream[d_index].real;
tens1_data_stream[d_index].imag += tens2_data_stream[d_index].imag;
}
}
Целевой компьютер имеет процессор Intel(R) Xeon(R) Platinum 8168 с тактовой частотой 2,70 ГГц, 24 ядра, кэш-память L1 32 КБ, кэш-память L2 1 МБ и кэш-память L3 33 МБ. Общая пропускная способность памяти составляет 115 ГБ/с.
Ниже показано, как мой код масштабируется с размером задачи S = N x N x N.
Может ли кто-нибудь сказать мне информацию, которую я предоставил, если:
Заранее спасибо.
РЕДАКТИРОВАТЬ:
Теперь я изобразил производительность в GFLOP/s с 24 ядрами и 48 ядрами (два узла NUMA, один и тот же процессор). Выглядит так:
А теперь графики сильного и слабого масштабирования:
Примечание: Я измерил полосу пропускания, и оказалось, что она составляет 105 ГБ/с.
Вопрос: Значение странного пика при 6 потоках/размере задачи 90x90x90x16 B на графике слабого масштабирования мне не очевидно. Кто-нибудь может это прояснить?
О вашем редактировании: 1. еще много точек данных, пожалуйста. Посмотрите размеры своего кеша (вероятно, 64 КБ и 2 МБ или около того) и убедитесь, что вы попали в размеры внутри/снаружи. 2. с 48 ядрами я бы протестировал 4,8,12,....44,46,48, чтобы увидеть, есть ли у вас явление «исчерпания пропускной способности». Постройте это для достаточно большого набора данных. И используйте размер набора данных в мегабайтах для построения графика, а не параметр вашей проблемы N. 3. с 2 узлами Numa проверьте влияние параметра OMP_PROC_BIND
.
Все хорошие моменты, возможно, были частью ответа :)
Еще один полезный способ показать данные — построить график «Параллельная эффективность»; Измерение параллельной производительности Насколько хорошо масштабируется мое приложение? — хорошее введение.
Еще больше объяснений принципов масштабирования: theartofhpc.com/istc/parallel.html#Theoreticalconcepts.
Ваш график имеет примерно правильную форму: крошечные массивы должны помещаться в кеш L1, а значит, получать очень высокую производительность. Массивы в мегабайт или около того помещаются в L2 и получают более низкую производительность, кроме того, вы должны выполнять потоковую передачу из памяти и получать низкую производительность. Таким образом, связь между размером задачи и временем выполнения действительно должна становиться все круче с увеличением размера. Однако результирующий график (кстати, количество операций в секунду встречается чаще, чем просто время выполнения) должен иметь ступенчатую структуру по мере того, как вы достигаете последовательных границ кеша. Я бы сказал, что у вас недостаточно данных, чтобы продемонстрировать это.
Кроме того, обычно вы повторяете каждый «эксперимент» несколько раз, чтобы 1. сгладить статистические сбои и 2. убедиться, что данные действительно находятся в кеше.
Поскольку вы пометили этот «openmp», вам также следует изучить возможность выбора заданного размера массива и изменения количества ядер. Затем вы должны получить более или менее линейный прирост производительности, пока у процессора не будет достаточно пропускной способности для поддержки всех ядер.
Комментатор упомянул о концепциях сильного/слабого масштабирования. Сильное масштабирование означает: при заданном размере задачи использовать все больше и больше ядер. Это должно повысить производительность, но с уменьшающейся отдачей, поскольку накладные расходы начинают доминировать. Слабое масштабирование означает: оставить размер задачи на процесс/поток/что угодно постоянным и увеличить количество обрабатывающих элементов. Это должно дать вам почти линейное увеличение производительности до тех пор, пока, как я уже говорил, у вас не закончится полоса пропускания. То, что вы, кажется, делаете, на самом деле не является ни тем, ни другим: вы делаете «оптимистическое масштабирование»: увеличиваете размер проблемы, а все остальное остается постоянным. Это должно дать вам все лучшую и лучшую производительность, за исключением эффектов кеша, как я указал.
Поэтому, если вы хотите сказать, что «этот код масштабируется», вы должны решить, при каком сценарии. Как бы то ни было, ваша цифра в 200 Гбит/с вполне правдоподобна. Это зависит от деталей вашей архитектуры, но для относительно недавнего узла Intel это звучит разумно.
Существует сильное масштабирование и слабое масштабирование. Для сильного масштабирования вы оставляете размер задачи постоянным и варьируете вычислительные ресурсы (потоки) и хотите, чтобы время шло обратно пропорционально ресурсам. Для слабого масштабирования вы меняете оба одновременно (например, удваиваете размер задачи и количество потоков) и хотите, чтобы время оставалось постоянным.