2016-11-23 22 views
3

Я изучаю, как запустить процесс на выделенном CPU, чтобы избежать контекстных переключений. На моем Ubuntu я выделил два процессора, используя параметры ядра «isolcpus = 3,7» и «irqaffinity = 0-2,4-6». Я уверен, что он правильно учтен:Невозможно избежать контекстных переключений процесса, запущенного отдельно на CPU

$ cat /proc/cmdline 
BOOT_IMAGE=/boot/vmlinuz-4.8.0-27-generic root=UUID=58c66f12-0588-442b-9bb8-1d2dd833efe2 ro quiet splash isolcpus=3,7 irqaffinity=0-2,4-6 vt.handoff=7 

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

$ stress -c 24 
stress: info: [31717] dispatching hogs: 24 cpu, 0 io, 0 vm, 0 hdd 

А на второй, используя «сверху» я могу проверить использование моих процессоров:

top - 18:39:07 up 2 days, 20:48, 18 users, load average: 23,15, 10,46, 4,53 
Tasks: 457 total, 26 running, 431 sleeping, 0 stopped, 0 zombie 
%Cpu0 :100,0 us, 0,0 sy, 0,0 ni, 0,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu1 : 98,7 us, 1,3 sy, 0,0 ni, 0,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu2 : 99,3 us, 0,7 sy, 0,0 ni, 0,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu3 : 0,0 us, 0,0 sy, 0,0 ni,100,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu4 : 95,7 us, 4,3 sy, 0,0 ni, 0,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu5 : 98,0 us, 2,0 sy, 0,0 ni, 0,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu6 : 98,7 us, 1,3 sy, 0,0 ni, 0,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
%Cpu7 : 0,0 us, 0,0 sy, 0,0 ni,100,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st 
KiB Mem : 7855176 total, 385736 free, 5891280 used, 1578160 buff/cache 
KiB Swap: 15624188 total, 10414520 free, 5209668 used. 626872 avail Mem 

процессоры 3 и 7 свободны в то время как 6 других единиц полностью заняты. Хорошо.


Для остальной части моего теста, я буду использовать небольшое приложение, которое делает почти чистую обработку

  1. Он использует два Int буферов одного и того же размера
  2. Он читает один-на - все значения первого буфера
    • каждое значение является случайным индексом во втором буфере
  3. Он считывает значение в индексе во втором буфере
  4. Он суммирует все значения, взятые из второго буфера
  5. Это все предыдущие шаги для большего и большего
  6. В конце, я печать количество и непроизвольного контекст CPU переходит

Я сейчас изучаю мое заявление, когда я запускаю его:

  1. на неизолированный CPU
  2. на изолированном CPU

я сделать это с помощью следующих командных строк:

$ ./TestCpuset    ### launch on any non-isolated CPU 
$ taskset -c 7 ./TestCpuset ### launch on isolated CPU 7 

При запуске на любом процессоре, количество переключений контекста изменения от 20 ... ...

При запуске на изолированном ЦП количество переключателей контекста почти постоянное (между 10 и 20), даже если я запускаю параллельно «напряжение -c 24». (выглядит вполне нормально)

Но мой вопрос: почему он не 0 абсолютно 0? Когда переключатель выполняется в процессе, он должен заменить его другим процессом? Но в моем случае нет другого процесса для замены!

У меня есть гипотезы, что вариант «isolcpus» будет изолировать процессора формировать любой процесс (если не указано процесса сродство процессора будет дано, например, то, что делается с «набор задач»), но не из задачи ядра. Однако, я не нашел в документации об этом

Я был бы признателен за любую помощь, чтобы достичь 0 контекстно-переключателей

FYI, этот вопрос закрыт на другой я уже открыл: Cannot allocate exclusively a CPU for my process

Вот код программы я использую:

#include <limits.h> 
#include <iostream> 
#include <unistd.h> 
#include <sys/time.h> 
#include <sys/resource.h> 

const unsigned int BUFFER_SIZE = 4096; 

using namespace std; 


class TimedSumComputer 
{ 

public: 
    TimedSumComputer() : 
    sum(0), 
    bufferSize(0), 
    valueBuffer(0), 
    indexBuffer(0) 
    {} 


public: 
    virtual ~TimedSumComputer() 
    { 
    resetBuffers(); 
    } 


public: 
    void init(unsigned int bufferSize) 
    { 
    this->bufferSize = bufferSize; 
    resetBuffers(); 
    initValueBuffer(); 
    initIndexBuffer(); 
    } 


private: 
    void resetBuffers() 
    { 
    delete [] valueBuffer; 
    delete [] indexBuffer; 
    valueBuffer = 0; 
    indexBuffer = 0; 
    } 


    void initValueBuffer() 
    { 
    valueBuffer = new unsigned int[bufferSize]; 
    for (unsigned int i = 0 ; i < bufferSize ; i++) 
    { 
     valueBuffer[i] = randomUint(); 
    } 
    } 


    static unsigned int randomUint() 
    { 
    int value = rand() % UINT_MAX; 
    return value; 
    } 


protected: 
    void initIndexBuffer() 
    { 
    indexBuffer = new unsigned int[bufferSize]; 
    for (unsigned int i = 0 ; i < bufferSize ; i++) 
    { 
     indexBuffer[i] = rand() % bufferSize; 
    } 
    } 


public: 
    unsigned int getSum() const 
    { 
    return sum; 
    } 


    unsigned int computeTimeInMicroSeconds() 
    { 
    struct timeval startTime, endTime; 

    gettimeofday(&startTime, NULL); 
    unsigned int sum = computeSum(); 
    gettimeofday(&endTime, NULL); 

    return ((endTime.tv_sec - startTime.tv_sec) * 1000 * 1000) + (endTime.tv_usec - startTime.tv_usec); 
    } 


    unsigned int computeSum() 
    { 
    sum = 0; 

    for (unsigned int i = 0 ; i < bufferSize ; i++) 
    { 
     unsigned int index = indexBuffer[i]; 
     sum += valueBuffer[index]; 
    } 

    return sum; 
    } 


protected: 
    unsigned int sum; 
    unsigned int bufferSize; 
    unsigned int * valueBuffer; 
    unsigned int * indexBuffer; 

}; 



unsigned int runTestForBufferSize(TimedSumComputer & timedComputer, unsigned int bufferSize) 
{ 
    timedComputer.init(bufferSize); 

    unsigned int timeInMicroSec = timedComputer.computeTimeInMicroSeconds(); 
    cout << "bufferSize = " << bufferSize << " - time (in micro-sec) = " << timeInMicroSec << endl; 
    return timedComputer.getSum(); 
} 



void runTest(TimedSumComputer & timedComputer) 
{ 
    unsigned int result = 0; 

    for (unsigned int i = 1 ; i < 10 ; i++) 
    { 
    result += runTestForBufferSize(timedComputer, BUFFER_SIZE * i); 
    } 

    unsigned int factor = 1; 
    for (unsigned int i = 2 ; i <= 6 ; i++) 
    { 
    factor *= 10; 
    result += runTestForBufferSize(timedComputer, BUFFER_SIZE * factor); 
    } 

    cout << "result = " << result << endl; 
} 



void printPid() 
{ 
    cout << "###############################" << endl; 
    cout << "Pid = " << getpid() << endl; 
    cout << "###############################" << endl; 
} 



void printNbContextSwitch() 
{ 
    struct rusage usage; 
    getrusage(RUSAGE_THREAD, &usage); 
    cout << "Number of voluntary context switch: " << usage.ru_nvcsw << endl; 
    cout << "Number of involuntary context switch: " << usage.ru_nivcsw << endl; 
} 



int main() 
{ 
    printPid(); 

    TimedSumComputer timedComputer; 
    runTest(timedComputer); 

    printNbContextSwitch(); 

    return 0; 
} 
+0

Откуда берутся ваши данные? Используете ли вы больше памяти, чем физическая машина? Я ожидаю, что доступ к выгружаемому разделу памяти заставит увеличить счетчик переключения контекста, когда процесс приостанавливается, ожидая операции подкачки. –

+0

Программа, которую я использую, представляет собой просто простую тестовую программу, она только обращается к буферам, которые инициализируются случайными значениями (см. Функцию rand()) –

ответ

1

Потенциально любой системный вызов с ould включает контекст переключателя. Когда вы получаете доступ к выгружаемой памяти, это может также увеличить счетчик контекста. Чтобы достичь 0 переключателей контекста, вам необходимо заставить ядро ​​хранить всю память, которую ваша программа использует, сопоставленную с ее адресным пространством, и вам нужно быть уверенным, что ни один из вызовов, которые вы вызываете, влечет за собой контекстный переключатель. Я считаю, что это возможно на ядрах с RT-патчами, но, вероятно, их трудно достичь на стандартном ядре дистрибутива.

+0

Большое спасибо за этот ответ. Я почти уверен, что в моем простом примере (код которого приведен выше) вся память, используемая моей программой, остается отображаемой => нет страницы. –

+0

Кроме того, у меня добровольно практически нет системных вызовов, кроме: 1) те, которые вводит новый/delete 2) getrusage() ??? 3) cout Возможно, я ошибаюсь, но контекстные переключатели, связанные с syscalls, записываются в «добровольных переключателях контекста», но на моей стороне основная проблема связана с «непроизвольными» –

1

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

Я включил планировщик трассировку в то время как мое приложение работало так:

# sudo bash 
# cd /sys/kernel/debug/tracing 
# echo 1 > options/function-trace ; echo function_graph > current_tracer ; echo 1 > tracing_on ; echo 0 > tracing_max_latency ; taskset -c 7 [path-to-my-program]/TestCpuset ; echo 0 > tracing_on 
# cat trace 

Как была запущена моя программа на CPU- (taskset -c 7), я должен отфильтровать вывод «трассировки»

# grep " 7)" trace 

Я могу затем искать переходы от одного процесса к другому:

# grep " 7)" trace | grep "=>" 
... 
7) TestCpu-4753 => kworker-5866 
7) kworker-5866 => TestCpu-4753 
7) TestCpu-4753 => watchdo-26 
7) watchdo-26 => TestCpu-4753 
7) TestCpu-4753 => kworker-5866 
7) kworker-5866 => TestCpu-4753 
7) TestCpu-4753 => kworker-5866 
7) kworker-5866 => TestCpu-4753 
7) TestCpu-4753 => kworker-5866 
7) kworker-5866 => TestCpu-4753 
... 

Бинго! Кажется, что переключение контекста я отслеживать переходы к:

  • kworker
  • сторожевого

теперь я должен найти:

  • какие именно эти процессы/потоки? (кажется, что они обрабатываются ядром)
  • Могу ли я избежать их запуска на своих выделенных процессорах?

Для конечно, еще раз я был бы признателен за любую помощь :-P

+0

. Я нашел эту действительно интересную страницу: [избегать запуска демонов в выделенных ядрах процессора] (http://stackoverflow.com/questions/40081780/avoid-daemon-running-in-dedicated-cpu-cores/40082778) –

+1

Кажется, что сторожевой таймер может быть отключен с помощью опции ядра Linux 'nowatchdog ' –

0

Ради тех, кто найти это через Google (как я), /sys/devices/virtual/workqueue/cpumask управления, где ядро ​​может стоять в очереди работ в очереди с WORK_CPU_UNBOUND (Don не заботясь о том, какой процессор). Начиная с этого ответа, он не настроен на ту же маску, что и по умолчанию isolcpus.

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