почему у perf такие высокие переключатели контекста? - PullRequest
0 голосов
/ 12 сентября 2018

Я пытался понять linux perf, и обнаружил какое-то действительно запутанное поведение:

Я написал простой многопоточный пример с одним потоком, прикрепленным к каждому ядру; каждый поток выполняет вычисления локально и не связывается друг с другом (см. test.cc ниже). Я думал, что этот пример должен иметь действительно низкие, если не нулевые, переключатели контекста. Однако использование linux perf для профилирования примера показывает тысячи переключений контекста - намного больше, чем я ожидал. Я также профилировал команду linux sleep 20 для сравнения, показывая гораздо меньше переключений контекста.

Этот профиль не имеет для меня никакого смысла. Что вызывает так много переключений контекста?

> sudo perf stat -e sched:sched_switch ./test
 Performance counter stats for './test':

                 6,725  sched:sched_switch                                          

      20.835 seconds time elapsed

> sudo perf stat -e sched:sched_switch sleep 20

 Performance counter stats for 'sleep 20':

                 1      sched:sched_switch                                          

      20.001 seconds time elapsed

Для воспроизведения результатов, пожалуйста, введите следующий код:

perf stat -e context-switches sleep 20
perf stat -e context-switches ./test

Чтобы скомпилировать исходный код, введите следующий код:

g++ -std=c++11 -pthread -o test test.cc
// test.cc
#include <iostream>
#include <thread>
#include <vector>

int main(int argc, const char** argv) {
  unsigned num_cpus = std::thread::hardware_concurrency();
  std::cout << "Launching " << num_cpus << " threads\n";

  std::vector<std::thread> threads(num_cpus);
  for (unsigned i = 0; i < num_cpus; ++i) {
    threads[i] = std::thread([i] {
      int j = 0;
      while (j++ < 100) {
        int tmp = 0;
        while (tmp++ < 110000000) { }
      }
    });

    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(i, &cpuset);
    int rc = pthread_setaffinity_np(threads[i].native_handle(),
                                    sizeof(cpu_set_t), &cpuset);
    if (rc != 0) {
      std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n";
    }
  }

  for (auto& t : threads) {
    t.join();
  }
  return 0;
}

Ответы [ 2 ]

0 голосов
/ 13 сентября 2018

Вы можете использовать sudo perf sched record -- ./test, чтобы определить, какие процессы запланированы для выполнения вместо одного из потоков вашего приложения.Когда я выполняю эту команду в своей системе, я получаю:

sudo perf sched record -- ./test
Launching 4 threads
[ perf record: Woken up 10 times to write data ]
[ perf record: Captured and wrote 23.886 MB perf.data (212100 samples) ]

Обратите внимание, что у меня четыре ядра, а имя исполняемого файла - test.perf sched произвел выборку всех событий, связанных с планировщиком, и вывел данные в файл с именем perf.data по умолчанию.Размер файла составляет около 23 МБ и содержит около 212100 выборочных событий.Продолжительность выборки будет с момента начала perf до момента окончания test.

Вы можете использовать sudo perf sched map для печати всех записанных событий в хорошем формате, который выглядит следующим образом:

             *.         448826.757400 secs .  => swapper:0
              *A0       448826.757461 secs A0 => perf:15875
          *.   A0       448826.757477 secs 
  *.       .   A0       448826.757548 secs 
   .       .  *B0       448826.757601 secs B0 => migration/3:22
   .       .  *.        448826.757608 secs 
  *A0      .   .        448826.757625 secs 
   A0     *C0  .        448826.757775 secs C0 => rcu_sched:7
   A0     *.   .        448826.757777 secs 
  *D0      .   .        448826.757803 secs D0 => ksoftirqd/0:3
  *A0      .   .        448826.757807 secs 
   A0 *E0  .   .        448826.757862 secs E0 => kworker/1:3:13786
   A0 *F0  .   .        448826.757870 secs F0 => kworker/1:0:5886
   A0 *G0  .   .        448826.757874 secs G0 => hud-service:1609
   A0 *.   .   .        448826.758614 secs 
   A0 *H0  .   .        448826.758714 secs H0 => kworker/u8:2:15585
   A0 *.   .   .        448826.758721 secs 
   A0  .  *I0  .        448826.758740 secs I0 => gnome-terminal-:8878
   A0  .   I0 *J0       448826.758744 secs J0 => test:15876
   A0  .   I0 *B0       448826.758749 secs 

Двухбуквенные имена A0, B0, C0, E0 и т. Д. Являются короткими именами, присваиваемыми perf каждому потоку, работающему в системе.Первые четыре столбца показывают, какой поток работал на каждом из четырех ядер.Например, в строке от второй к последней вы можете видеть, что первый поток был создан в вашем цикле for.Имя, назначенное этой теме: J0.Поток работает на четвертом ядре.Звездочка указывает, что она только что была переключена на контекст из какого-то другого потока.Без звездочки это означает, что один и тот же поток продолжал работать на том же ядре еще один интервал времени.Точка представляет собой простое ядро.Чтобы определить имена для всех четырех потоков, выполните следующую команду:

sudo perf sched map | grep 'test'

В моей системе это напечатает:

   A0  .   I0 *J0       448826.758744 secs J0 => test:15876
   J0  A0 *K0  .        448826.758868 secs K0 => test:15878
   J0 *L0  K0  .        448826.758889 secs L0 => test:15877
   J0  L0  K0 *M0       448826.758894 secs M0 => test:15879

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

  *G1  L0  K0  M0       448826.822555 secs G1 => firefox:2384

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

Если вы хотите, чтобы все слоты планировщика занимали хотя бы один из ваших потоков, то вы можете использовать следующую команду:

sudo perf sched map > mydata
grep -E 'J0|K0|L0|M0' mydata > mydata2
wc -l mydata
wc -l mydata2

Последние две команды сообщают вам, сколько строк (временных интервалов) было запущено хотя бы в одном потоке вашего приложения.Вы можете сравнить это с общим количеством временных интервалов.Поскольку имеется четыре ядра, общее количество временных интервалов планировщика равно 4 * (количество временных интервалов).Затем вы можете делать все виды ручных вычислений и точно выяснить, что произошло.

0 голосов
/ 12 сентября 2018

Мы не можем точно сказать вам, что запланировано - но вы можете узнать себя, используя perf.

perf record -e sched:sched_switch ./test

Обратите внимание, что для этого требуются смонтированные debugfs и права root. Теперь perf report даст вам обзор того, на что переключался планировщик (или полный список см. В perf script). Теперь в вашем коде нет ничего очевидного, что могло бы вызвать переключение контекста (например, сон, ожидание ввода-вывода), поэтому, скорее всего, это еще одна задача, запланированная на этих ядрах.

Причина, по которой sleep почти не имеет переключений контекста, проста. Он почти сразу засыпает - это один из переключателей контекста. Пока задача не активна, она не может быть перемещена другой задачей.

...