Linux на четырехъядерном процессоре: один исполняемый файл, 4 процесса

У меня есть 4 исполняемых файла, которые выполняют очень сложные задачи, каждая из этих программ может принимать почти 100% мощности одного ядра четырехъядерного процессора, что приводит к почти 25% от общей мощности процессора. Поскольку все эти программы используют аппаратные ресурсы, которые нельзя разделить между несколькими процессами, я хочу запустить один исполняемый файл, который порождает 3 дочерних процесса, которые, в свою очередь, занимают остальные три ядра. Я нахожусь в Linux, и я использую С++ 11. Большая часть сложного кода работает в своем собственном классе, а самая сложная часть выполняется в функции, которую я обычно называю Process(), поэтому у меня есть 4 объекта, каждый со своим собственным Process(), который при запуске принимает 100% одноядерный.

Я пробовал использовать OpenMP, но я не думаю, что это лучшее решение, так как у меня нет контроля над связностью процессора. Также использование std::thread не является хорошей идеей, потому что потоки наследуют основной процесс "сродство к процессору". В Linux я думаю, что могу сделать это с помощью fork(), но я понятия не имею, как создается вся структура.

Это может быть связано с моим другим вопросом, который частично остался без ответа, возможно, потому, что я пытался использовать неправильный подход, который работает в некоторых случаях, но не в моем случае.

Примером псевдокода может быть следующее:

int main()
{
 // ...init everything...
 // This alone takes 100% of a single core
 float out1 = object1->Process(); 
 // This should be spawned as a child process running on another core
 float out2 = object2->Process();
 // on another core...
 float out3 ...
 // yet another core...
 float out4 ...
 // This should still run in the parent process
 float total_output = out1 + out2 + out3 + out4;
}
2 ответа

Вы можете использовать std::thread, чтобы передним интерфейсом был pthread_create(). Затем установите его сродство с sched_setaffinity() из самого потока.

Как вы просили, вот рабочий заглушка:

#include <sched.h>
#include <thread>
#include <list>
void thread_func(int cpu_index) {
 cpu_set_t cpuSet;
 CPU_ZERO(&cpuSet);
 CPU_SET(cpu_index, &cpuSet);
 sched_setaffinity(0, sizeof( cpu_set_t), &cpuSet);
 /* the rest of the thread body here */
}
using namespace std;
int main(int argc, char **argv) {
 if (argc != 2) exit(1);
 int n_cpus = atoi(argv[1]);
 list< shared_ptr< thread > > lot;
 for (int i=0; i<n_cpus; ++i)="" {="" lot.push_back(="" shared_ptr<thread="">(new thread(thread_func, i)));
 }
 for(auto tptr = lot.begin(); tptr != lot.end(); ++tptr) {
 (*tptr)->join();
 }
}
</n_cpus;></list></thread></sched.h>

Обратите внимание, что для оптимального поведения важно, чтобы каждый поток инициализировал свою память (т.е. конструирует свои объекты) в теле потока, если вы хотите, чтобы ваш код был оптимизирован также на многопроцессорных системах, поскольку в случае, если вы работаете система NUMA, страницы памяти распределяются по памяти, расположенной рядом с ЦП, используя их.

Например, вы можете посмотреть этот блог.

Однако это не проблема в вашем конкретном случае, поскольку вы имеете дело с одной процессорной системой или, более конкретно, с системой с одним numa node (многие современные процессоры AMD содержат два узла numa, даже если они находятся внутри один физический пакет), и все банки памяти прикреплены там.

Конечным эффектом использования sched_setaffinity() в этом контексте будет просто привязка каждого потока к определенному ядру.


Вам не нужно ничего программировать. Команда taskset изменяет близость процессора к текущему запущенному процессу или создает и устанавливает его для нового процесса.

Запуск одного исполняемого файла, который запускает другие программы, не отличается от выполнения программ напрямую, за исключением общей инициализации, подразумеваемой комментариями в вашей заглушке.

taskset 1 program_for_cpu_0 arg1 arg2 arg3...
taskset 2 program_for_cpu_1 arg1 arg2 arg3...
taskset 4 program_for_cpu_2 arg1 arg2 arg3...
taskset 8 program_for_cpu_3 arg1 arg2 arg3...

Я с подозрением отношусь к настройке аффинности процессора. Я еще не нашел реального использования для этого (кроме удовлетворения некоторой внутренней потребности в контроле).

Если CPU не идентичны каким-либо образом, не должно быть необходимости ограничивать процесс для конкретного процессора.

  • Ядро Linux обычно поддерживает процесс на одном CPU, если он не вступает в расширенное ожидание ввода/вывода, семафора и т.д.
  • Переключение процесса с одного процессора на другой не несет каких-либо особых накладных расходов, за исключением NUMA-конфигураций с локальной памятью на процессор. AFAIK, реализации ARM этого не делают.
  • Если процесс должен демонстрировать поведение, не связанное с ЦП, позволяя гибкому планировщику ядра переназначить процесс на теперь доступный ЦП, это должно улучшить производительность системы. Процессы, связанные с ЦП, не могут участвовать в доступности ресурсов.

licensed under cc by-sa 3.0 with attribution.