Как контролировать, на каком ядре выполняется процесс?

StackOverflow https://stackoverflow.com/questions/663958

Вопрос

Я могу понять, как можно написать программу, которая использует несколько процессов или потоков:разветвляйте() новый процесс и используйте IPC, или создайте несколько потоков и используйте такого рода механизмы связи.

Я также понимаю переключение контекста.То есть, имея только один процессор, операционная система планирует время для каждого процесса (а существует множество алгоритмов планирования), и таким образом мы добиваемся одновременного запуска нескольких процессов.

И теперь, когда у нас есть многоядерные процессоры (или многопроцессорные компьютеры), у нас могло бы быть два процесса, запущенных одновременно на двух отдельных ядрах.

Мой вопрос касается последнего сценария:как ядро управляет тем, на каком ядре выполняется процесс?Какие системные вызовы (в Linux или даже Windows) планируют процесс на определенном ядре?

Причина, по которой я спрашиваю:Я работаю над проектом для школы, где мы должны изучить недавнюю тему в области вычислительной техники, и я выбрал многоядерные архитектуры.Кажется, есть много материала о том, как программировать в такой среде (как следить за состоянием взаимоблокировки или гонки), но мало об управлении самими отдельными ядрами.Я хотел бы иметь возможность написать несколько демонстрационных программ и представить некоторые инструкции по сборке или C-код типа "Смотрите, я запускаю бесконечный цикл на 2-м ядре, посмотрите на всплеск загрузки процессора для это конкретное ядро".

Есть какие-нибудь примеры кода?Или учебные пособия?

Редактировать:Для пояснения - многие люди говорили, что это цель операционной системы, и что следует позволить операционной системе позаботиться об этом.Я полностью согласен!Но тогда то, что я спрашиваю (или пытаюсь понять), - это то, что на самом деле делает операционная система для этого.Не алгоритм планирования, а скорее "как только ядро выбрано, какие инструкции должны быть выполнены, чтобы это ядро начало получать инструкции?"

Это было полезно?

Решение

Как уже упоминали другие, сходство с процессором является Специфичная для операционной системы.Если вы хотите сделать это за пределами операционной системы, вас ждет масса удовольствия, и под этим я подразумеваю боль.

Тем не менее, другие упоминали SetProcessAffinityMask для Win32.Никто не упомянул способ установки привязки процессора к ядру Linux, и поэтому я это сделаю.Вам нужно использовать sched_setaffinity функция.Вот хороший учебник о том, как.

Другие советы

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

Чтобы сделать это в Windows, воспользуйтесь диспетчером задач, щелкните правой кнопкой мыши на процессе и выберите "Установить привязку".Вы можете сделать это программно в Windows, используя такие функции, как SetThreadAffinityMask, SetProcessAffinityMask или SetThreadIdealProcessor.

ETA:

Если вас интересует, как ОС на самом деле выполняет планирование, возможно, вы захотите ознакомиться с этими ссылками:

Статья в Википедии о переключении контекста

Статья в Википедии о планировании

Планирование в ядре Linux

В большинстве современных ОС операционная система планирует выполнение потока в ядре на короткий промежуток времени.Когда временной интервал истекает или поток выполняет операцию ввода-вывода, которая заставляет его добровольно отдавать ядро, ОС запланирует запуск другого потока на ядре (если есть какие-либо потоки, готовые к запуску).Какой именно поток запланирован, зависит от алгоритма планирования операционной системы.

Детали реализации того, как именно происходит переключение контекста, зависят от процессора и операционной системы.Обычно это включает переключение в режим ядра, ОС сохраняет состояние предыдущего потока, загружает состояние нового потока, затем переключается обратно в пользовательский режим и возобновляет только что загруженный поток.Статья о переключении контекста, на которую я ссылался выше, содержит немного больше подробностей об этом.

Ничто не говорит ядру "теперь начните запускать этот процесс".

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

Когда компьютер загружается, для простоты активируется только одно ядро / процессор и фактически выполняется любой код.Затем, если ОС поддерживает многопроцессорность, она активирует другие ядра с помощью какой-либо специфичной для системы инструкции, другие ядра, скорее всего, подхватываются точно из того же места, что и другое ядро, и запускаются оттуда.

Итак, что делает планировщик, так это просматривает внутренние структуры ОС (очередь задач / процессов / потоков), выбирает одну из них и помечает ее как запущенную в своем ядре.Тогда другие экземпляры планировщика, запущенные на других ядрах, не будут касаться его до тех пор, пока задача снова не перейдет в состояние ожидания (и не будет помечена как привязанная к определенному ядру).После того, как задача помечена как выполняемая, планировщик выполняет переключение в пользовательскую область с возобновлением задачи в том месте, где она была ранее приостановлена.

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

Сценарий становится более странным с более экзотическими моделями памяти (выше предполагается "обычное" линейное единое рабочее пространство памяти), где ядра не обязательно все видят одну и ту же память, и могут быть требования к извлечению кода из цепочек других ядер, но с этим намного проще справиться, просто привязав задачу к ядру (AFAIK архитектура Sony PS3 с SPU похожа на это).

В OpenMPI проект имеет библиотека для установки привязки к процессору на Linux в переносном виде.

Некоторое время назад я использовал это в проекте, и это сработало нормально.

Предостережение: Я смутно помню, что были некоторые проблемы с определением того, как операционная система нумерует ядра.Я использовал это в системе с 2 процессорами Xeon по 4 ядра в каждом.

Взгляните на cat /proc/cpuinfo могло бы помочь.На коробке, которую я использовал, это довольно странно.Уваренный выход находится в конце.

Очевидно, что равномерно пронумерованные ядра находятся на первом процессоре, а нечетно пронумерованные ядра - на втором процессоре.Однако, если я правильно помню, возникла проблема с кэшами.На этих процессорах Intel Xeon два ядра на каждом процессоре совместно используют свои кэши L2 (я не помню, есть ли у процессора кэш L3).Я думаю, что виртуальные процессоры 0 и 2 совместно использовали один кэш L2, 1 и 3 совместно использовали один, 4 и 6 совместно использовали один и 5 и 7 совместно использовали один.

Из-за этой странности (1,5 года назад я не мог найти никакой документации по нумерации процессов в Linux), я был бы осторожен при выполнении такого рода низкоуровневой настройки.Тем не менее, здесь явно есть какое-то применение.Если ваш код выполняется на нескольких типах машин, то, возможно, стоит выполнить такого рода настройку.Другое приложение было бы на каком-нибудь специфичном для домена языке, например Поток - это где компилятор мог бы выполнить эту грязную работу и вычислить разумное расписание.

processor       : 0
physical id     : 0
siblings        : 4
core id         : 0
cpu cores       : 4

processor       : 1
physical id     : 1
siblings        : 4
core id         : 0
cpu cores       : 4

processor       : 2
physical id     : 0
siblings        : 4
core id         : 1
cpu cores       : 4

processor       : 3
physical id     : 1
siblings        : 4
core id         : 1
cpu cores       : 4

processor       : 4
physical id     : 0
siblings        : 4
core id         : 2
cpu cores       : 4

processor       : 5
physical id     : 1
siblings        : 4
core id         : 2
cpu cores       : 4

processor       : 6
physical id     : 0
siblings        : 4
core id         : 3
cpu cores       : 4

processor       : 7
physical id     : 1
siblings        : 4
core id         : 3
cpu cores       : 4

Чтобы узнать количество процессоров, вместо использования /proc/cpuinfo просто запустите:

nproc

Для запуска процесса на группе определенных процессоров:

taskset --cpu-list 1,2 my_command 

скажет, что моя команда может выполняться только на процессоре 1 или 2.

Чтобы запустить программу на 4 процессорах, выполняющую 4 разные действия, используйте параметризацию.Аргумент программы подсказывает ей сделать что-то другое:

for i in `seq 0 1 3`;
do 
  taskset --cpu-list $i my_command $i;
done

Хорошим примером этого является обработка 8 миллионов операций в массиве, так что от 0 до (2mil-1) переходит к процессору 1, от 2mil до (4mil-1) к процессору 2 и так далее.

Вы можете посмотреть на нагрузку на каждый процесс, установив htop с помощью apt-get /yum и запустив в командной строке:

 htop

Как уже упоминали другие, он управляется операционной системой.В зависимости от операционной системы она может предоставлять вам системные вызовы, которые позволяют вам влиять на то, на каком ядре выполняется данный процесс, а может и не предоставлять.Однако обычно вам следует просто позволить операционной системе выполнять поведение по умолчанию.Если у вас 4-ядерная система с 37 запущенными процессами, и 34 из этих процессов находятся в спящем режиме, оставшиеся 3 активных процесса будут распределены по отдельным ядрам.

Скорее всего, вы увидите увеличение скорости при работе с привязками к ядру только в очень специализированных многопоточных приложениях.Например, предположим, что у вас есть система с 2 двухъядерными процессорами.Предположим, у вас есть приложение с 3 потоками, и два из них интенсивно работают с одним и тем же набором данных, в то время как третий поток использует другой набор данных.В этом случае вы получили бы наибольшую выгоду, имея два потока, которые взаимодействуют на одном процессоре, и третий поток на другом процессоре, поскольку тогда они могут совместно использовать кэш.ОС понятия не имеет, к какой памяти должен обращаться каждый поток, поэтому она может неправильно распределять потоки по ядрам.

Если вы заинтересованы в как операционная система, ознакомьтесь с планирование.Подробные сведения о многопроцессорности на x86 можно найти в Руководства разработчика программного обеспечения для архитектур Intel 64 и IA-32.Том 3A, главы 7 и 8 содержат соответствующую информацию, но имейте в виду, что эти руководства носят исключительно технический характер.

ОС знает, как это сделать, вам это не обязательно.Вы могли бы столкнуться со всевозможными проблемами, если бы указали, на каком ядре запускать, некоторые из которых действительно могли бы замедлить процесс.Позвольте операционной системе разобраться с этим, вам просто нужно запустить новый поток.

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

Я не знаю инструкций по сборке.Но функция Windows API - это Установите processaffinitymask.Вы можете видеть пример о чем-то, что я собрал некоторое время назад, чтобы запустить Picasa только на одном ядре

Linux sched_setaffinity C минимальный выполнимый пример

В этом примере мы получаем аффинити, изменяем его и проверяем, вступило ли оно в силу с sched_getcpu().

#define _GNU_SOURCE
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void print_affinity() {
    cpu_set_t mask;
    long nproc, i;

    if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_getaffinity");
        assert(false);
    } else {
        nproc = sysconf(_SC_NPROCESSORS_ONLN);
        printf("sched_getaffinity = ");
        for (i = 0; i < nproc; i++) {
            printf("%d ", CPU_ISSET(i, &mask));
        }
        printf("\n");
    }
}

int main(void) {
    cpu_set_t mask;

    print_affinity();
    printf("sched_getcpu = %d\n", sched_getcpu());
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);
    if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_setaffinity");
        assert(false);
    }
    print_affinity();
    /* TODO is it guaranteed to have taken effect already? Always worked on my tests. */
    printf("sched_getcpu = %d\n", sched_getcpu());
    return EXIT_SUCCESS;
}

Скомпилируйте и запустите с:

gcc -std=c99 main.c
./a.out

Пример вывода:

sched_getaffinity = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
sched_getcpu = 9
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 0

Это означает , что:

  • изначально все мои 16 ядер были включены, и процесс случайным образом запускался на ядре 9 (10-м).
  • после того, как мы установили привязку только к первому ядру, процесс был обязательно перемещен к ядру 0 (первому)

Также интересно запускать эту программу через taskset:

taskset -c 1,3 ./a.out

Который выдает вывод формы:

sched_getaffinity = 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 2
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 0

и вот мы видим, что это с самого начала ограничивало близость.

Это работает, потому что аффинити наследуется дочерними процессами, которые taskset является раздвоением: Как предотвратить наследование привязки процессора дочерним разветвленным процессом?

Протестировано в Ubuntu 16.04, Восходящий поток GitHub.

x86 голый металл

Если ты такой хардкорный: Как выглядит многоядерный язык ассемблера?

Как Linux это реализует

Как работает sched_setaffinity()?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top