В скором времени о моей проблеме:
У меня есть компьютер с 2 гнездами AMD Opteron 6272 и RAM объемом 64 ГБ.
Я запускаю одну многопоточную программу на всех 32 ядрах и получаю скорость на 15% меньше по сравнению со случаем, когда я запускаю 2 программы, каждый из которых имеет один 16-ядерный сокет.
Как сделать однопрограммную версию так же быстро, как две программы?
Подробнее:
У меня есть большое количество задач и вы хотите полностью загрузить все 32 ядра системы.
Поэтому я собираю задания в группах на 1000. Такая группа требует около 120 МБ входных данных и занимает около 10 секунд для завершения одного ядра. Чтобы сделать идеальный тест, я копирую эти группы 32 раза и используя цикл ITBB parallel_for
распределяет задачи между 32 ядрами.
Я использую pthread_setaffinity_np
, чтобы гарантировать, что система не будет переводить мои потоки между ядрами. И чтобы гарантировать, что все ядра используются concqutively.
Я использую mlockall(MCL_FUTURE)
, чтобы гарантировать, что система не переместит мою память между сокетами.
Итак, код выглядит так:
void operator()(const blocked_range<size_t> &range) const
{
for(unsigned int i = range.begin(); i != range.end(); ++i){
pthread_t I = pthread_self();
int s;
cpu_set_t cpuset;
pthread_t thread = I;
CPU_ZERO(&cpuset);
CPU_SET(threadNumberToCpuMap[i], &cpuset);
s = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
mlockall(MCL_FUTURE); // lock virtual memory to stay at physical address where it was allocated
TaskManager manager;
for (int j = 0; j < fNTasksPerThr; j++){
manager.SetData( &(InpData->fInput[j]) );
manager.Run();
}
}
}
Для меня важно только время вычислений, поэтому я готовлю входные данные в отдельный цикл parallel_for
. И не включайте время подготовки во время измерений.
void operator()(const blocked_range<size_t> &range) const
{
for(unsigned int i = range.begin(); i != range.end(); ++i){
pthread_t I = pthread_self();
int s;
cpu_set_t cpuset;
pthread_t thread = I;
CPU_ZERO(&cpuset);
CPU_SET(threadNumberToCpuMap[i], &cpuset);
s = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
mlockall(MCL_FUTURE); // lock virtual memory to stay at physical address where it was allocated
InpData[i].fInput = new ProgramInputData[fNTasksPerThr];
for(int j=0; j<fNTasksPerThr; j++){
InpData[i].fInput[j] = InpDataPerThread.fInput[j];
}
}
}
Теперь я запускаю все эти данные на 32 ядрах и вижу скорость ~ 1600 задач в секунду.
Затем я создаю две версии программы, а с taskset
и pthread
застрахованы от первого запуска на 16 ядрах первого сокета и второго - на втором сокете. Я запускаю их один рядом друг с другом, просто используя команду &
в оболочке:
program1 & program2 &
Каждая из этих программ достигает скорости ~ 900 задач/с. В общей сложности это > 1800 задач/с, что на 15% больше, чем однопрограммная версия.
Что мне не хватает?
Я считаю, что проблема может быть в библиотеках, которые я загружаю только в память потока сообщений. Это может быть проблема? Могу ли я копировать данные библиотек, чтобы они были доступны независимо на обоих сокетах?