Коротко о моей проблеме:Многопоточность: Почему две программы лучше, чем одна?
У меня есть компьютер с 2 гнездами AMD Opteron 6272 и 64 ГБ оперативной памяти.
Я запускаю одну многопоточную программу на всех 32 ядрах и получаю скорость на 15% меньше по сравнению со случаем, когда я запускаю 2 программы, каждая на одной 16-ядерной розетке.
Как сделать однопрограммную версию так же быстро, как две программы?
Подробнее:
У меня есть большое количество задач, и вы хотите, чтобы полностью загрузить все 32 ядра системы. Таким образом, я собираю задачи группами по 1000. Такая группа требует около 120 МБ входных данных и занимает около 10 секунд для завершения одного ядра. Чтобы сделать идеальный тест, я копирую эти группы 32 раза, а цикл ITBB parallel_for
распределяет задачи между 32 ядрами.
Я использую pthread_setaffinity_np
, чтобы обеспечить, чтобы мои потоки не перескакивали между ядрами. И чтобы гарантировать, что все ядра используются consequtively.
Я использую 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% больше, чем однопрограммная версия.
Что я пропущу?
Я считаю, что это может быть проблемой в библиотеках, которые я загружаю только в память только для мелочей. Это может быть проблема? Могу ли я копировать данные библиотек, чтобы они были доступны независимо в обоих сокетах?
Вы пробовали 32 однопоточных программы? –
32 однопоточных программ не будут иметь дело с проблемой, которая, скорее всего, будет распределяться по памяти в неправильном узле numa. У него только 2 узла, поэтому ему нужны только 2 программы, каждая из которых привязана к одному узлу. –
Numa node ?? Я понятия не имею, что это такое, но это звучит так хорошо, что я собираюсь это выяснить. – Dennis