Как установить сходство на нескольких процессорах по sched_setaffinity
Я хочу установить сродство на нескольких процессорах sched_affinity
следующее.
void
pin(pid_t t, int cpu)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
sched_setaffinity(t, sizeof(cpu_set_t), &cpuset);
}
Моя среда состоит из 32 ядер, где 4 процессора существуют, а один процессор имеет 8 ядер.
Я хочу, чтобы потоки 0 ~ 7 работали на том же процессоре, а потоки 8 ~ 15 работали на том же процессоре и так далее.
Интересно, что для установки переменной CPU в CPU_SET.
Это устанавливается как идентификатор потока, если номера ядер распределены как наивные, то есть cpu0 имеет 0-е ядро и 1-е ядро, и 2-е ядро,..., а cpu1 имеет 8-е ядро, 9-е ядро,....
С одной стороны, cpu устанавливается как правило циклического перебора, если номера ядер распределяются как правило циклического перебора, то есть cpu0 имеет 0-е ядро и 4-е ядро, и 8-е ядро,..., а cpu1 имеет 1-е ядро и 5-е ядро,....
Какое правило я должен установить переменную CPU, простое правило или правило циклического перебора?
1 ответ
В Linux (и других ОС) программист может установить привязку к процессору, т.е. разрешенные процессоры, на которые ядро может планировать этот процесс. После fork() процессы наследуют сродство родительского процессора. Это очень удобно, если кто-то хочет по каким-либо причинам ограничивать процессоры.
Например, можно ограничить
- процессы определенных пользователей только к одному ЦП, в то время как другие пользователи оставляют остальную часть CPUS (см. man 7 cpuset).
- процесс к процессору, который "ближе" к чему-либо, например, ограничивает процесс, который осуществляет связь с ядрами в сокете, который напрямую подключен к сетевой карте (NIC или HCA).
В целом, может быть полезно ограничить процесс / поток определенными ядрами или сокетом, чтобы не планировать их на ОС - это максимизирует преимущества кэша L1/L2 (при закреплении на ядрах) или L3/LLC кеш (при закреплении на сокеты).
Относительно вашего вопроса о "Распределении потоков". В процессе разработки процессоров появилось симметричное многопоточность (SMT) или гиперпоточность (как их называет Intel), которое вводит 2 логических ядра (например, Intel Xeon) или даже 4 логических ядра (например, Intel Knights Landing, IBM Power) на физическое ядро. Эти логические ядра также представлены как "CPU" в процессоре выше. Более того, некоторые процессоры навязывают домены NUMA, где доступ к памяти из одного ядра в его "собственную" память быстрый, а доступ к памяти другого ядра в другом домене NUMA медленнее...
Итак, как показывают некоторые из приведенных выше комментариев: это зависит! Если ваши потоки взаимодействуют друг с другом (через разделяемую память), то они должны находиться близко в одном и том же кэше. Если ваши потоки используют одни и те же функциональные блоки (например, FPU), то планирование двух на одном физическом ядре (с двумя логическими ядрами, т.е. Hyperthread) может отрицательно сказаться на производительности.
Чтобы поиграть, пожалуйста, найдите следующий код:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <pthread.h>
// The following is Linux-specific
#include <syscall.h> // For syscall to gettid()
#include <sched.h> // sched_[gs]etaffinity require _GNU_SOURCE
#define ERROR(t, e) do { \
const int __error = (e); \
fprintf (stderr, "ERROR: %s error:%d [%s] errno:%d [%s]\n", \
(t), __error, strerror(__error), errno, strerror(errno)); \
exit(__error); \
} while(0)
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
/* Local function definitions */
void print_schedaffinity(const char * text, const cpu_set_t cpuset, const int max_cpus);
void * thread_func(void * arg);
/* Local type definitions */
struct thread_data {
pthread_t thread;
int max_cpu;
int thread_num;
void * thread_work;
};
/* The highest value for CPU to be specified in cpuset in a call to
* sched_setaffinity -- otherwise, we get returned -1 and errno==EINVAL
*/
static int max_cpu_available = 0;
/* Local function declarations */
void print_schedaffinity(const char * text, const cpu_set_t cpuset, const int max_cpus) {
const int max = MIN(8*sizeof(cpu_set_t), max_cpus);
int i;
printf("PRINT CPU AFFINITY %s:\n", text);
printf("cpus:\t");
for (i = 0; i < max; i++) {
printf (" %3d", i);
if (i % 8 == 7)
printf(" | ");
}
printf("\nmask:\t");
for (i = 0; i < max; i++) {
if (CPU_ISSET(i, &cpuset))
printf (" X");
else
printf (" ");
if (i % 8 == 7)
printf(" | ");
}
printf("\n");
}
void * thread_func(void * arg) {
struct thread_data * thread_data = (struct thread_data *)arg;
const size_t sizeof_cpuset = sizeof(cpu_set_t);
char print_buffer[64];
cpu_set_t cpuset;
long tid;
int rc;
CPU_ZERO(&cpuset);
CPU_SET(thread_data->thread_num % max_cpu_available, &cpuset);
/* We set the affinity of the CALLING thread, aka 0 */
tid = syscall(SYS_gettid);
printf("PID:%ld tid:%ld thread_num:%d\n",
getpid(), tid, thread_data->thread_num);
rc = sched_setaffinity(0, sizeof_cpuset, &cpuset);
if (0 != rc)
ERROR("sched_setaffinity", rc);
/* Dooo SCHTUF now */
/* Somewhat sort the output... */
sleep (thread_data->thread_num);
snprintf (print_buffer, sizeof(print_buffer),
"in thread %d after sched_setaffinity", thread_data->thread_num);
print_schedaffinity(print_buffer, cpuset, 8);
return NULL;
}
int main (int argc, char * argv[])
{
const int NUM = 8;
const pid_t pid = getpid();
const size_t size_cpu_set = sizeof(cpu_set_t);
cpu_set_t cpuset;
int rc;
int i;
/* Get, and print the original CPU affinity setting (scheduling is not limited, i.e. all cores may run this PID) */
CPU_ZERO (&cpuset);
rc = sched_getaffinity(pid, size_cpu_set, &cpuset);
if (0 != rc)
ERROR("sched_getaffinity", rc);
print_schedaffinity("in main", cpuset, 8);
/* Search for the last / highest cpu being set -- claim, that this is the max cpu to be set, cough */
for (i = 0; i < 8 * size_cpu_set; i++) {
if (!CPU_ISSET(i, &cpuset)) {
max_cpu_available = i;
break;
}
}
/* Limit the process to the first core, only */
CPU_ZERO (&cpuset);
CPU_SET (0, &cpuset);
rc = sched_setaffinity (pid, size_cpu_set, &cpuset);
if (0 != rc)
ERROR("sched_setaffinity", rc);
print_schedaffinity("in main after sched_setaffinity", cpuset, 8);
/* Let's start NUM threads and have them limit their scheduling */
sleep(1);
struct thread_data * thread_data = (struct thread_data*)malloc(sizeof(struct thread_data) * NUM);
for (i = 0; i < NUM; i++) {
thread_data[i].thread_num = i;
pthread_create (&thread_data[i].thread, NULL, thread_func, &thread_data[i]);
}
/* And wait for them to finish... */
for (i = 0; i < NUM; i++) {
pthread_join (thread_data[i].thread, NULL);
}
return 0;
}
Изменить: Следует уточнить Apple, так как OSX 10.5 (Leopard) предлагает Affinity, как в https://developer.apple.com/library/mac/releasenotes/Performance/RN-AffinityAPI/