【问题标题】:Cannot avoid context-switches on a process launched alone on a CPU无法避免在 CPU 上单独启动的进程的上下文切换
【发布时间】:2017-04-08 00:55:15
【问题描述】:

我正在研究如何在专用 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”我可以检查我的 CPU 的使用情况:

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 

CPU 3 和 7 空闲,而其他 6 个 CPU 完全忙碌。很好。


在接下来的测试中,我将使用一个几乎可以进行纯处理的小型应用程序

  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

在任何 CPU 上启动时,上下文切换的数量从 20 变为...数千

在独立的 CPU 上启动时,上下文切换的数量几乎是恒定的(在 10 到 20 之间),即使我并行启动“stress -c 24”。(看起来很正常)

但我的问题是:为什么不是 0 绝对是 0?对一个进程进行切换时,是为了将其替换为另一个进程吗?但就我而言,没有其他过程可以替代!

我有一个假设是“isolcpus”选项会隔离 CPU 形成任何进程(除非该进程的 CPU 亲和性是 给定的,例如使用“taskset”完成的操作)而不是内核任务。 但是,我没有找到有关它的文档

如果能获得 0 个上下文切换的帮助,我将不胜感激

仅供参考,这个问题已关闭我之前打开的另一个问题: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;
}

【问题讨论】:

  • 您的数据来自哪里?您使用的内存是否超过了机器的物理内存?我希望当进程在等待分页操作时暂停时,访问内存的分页部分将强制增加上下文切换计数器。
  • 我正在使用的程序只是一个简单的测试程序,它只访问用随机值初始化的缓冲区(参见 rand() 函数)

标签: performance linux-kernel scheduler affinity context-switch


【解决方案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 7 上启动(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
  • 看门狗

我现在必须找到:

  • 这些进程/线程到底是什么? (好像是内核处理的)
  • 我可以避免它们在我的专用 CPU 上运行吗?

当然,再次感谢任何帮助:-P

【讨论】:

【解决方案2】:

任何系统调用都可能涉及上下文切换。当您访问分页内存时,它也可能会增加上下文切换计数。要达到 0 个上下文切换,您需要强制内核将程序使用的所有内存映射到其地址空间,并且您需要确保您调用的任何系统调用都不需要上下文切换。我相信在带有 RT 补丁的内核上可能是可能的,但在标准发行版内核上可能很难实现。

【讨论】:

  • 非常感谢您的回答。我几乎可以肯定,在我的简单示例(上面提供了其代码)中,我的程序使用的所有内存都保持映射 => 没有页面输出。
  • 此外,我自愿几乎没有系统调用,除了:1)那些由 new / delete 引入的系统调用 2)getrusage() ??? 3) cout 我可能错了,但是系统调用所涉及的上下文切换记录在“自愿上下文切换”中,但在我这边,主要问题是“非自愿”的
【解决方案3】:

为了那些通过谷歌找到这个的人(比如我),/sys/devices/virtual/workqueue/cpumask 控制内核可以在哪里排队工作与WORK_CPU_UNBOUND 排队(不要关心哪个 cpu)。在撰写此答案时,它未设置为与 isolcpus 默认操作的掩码相同。

一旦我将其更改为不包括我的隔离 CPU,我发现切换到我的关键线程的上下文数量明显减少(但不是零)。我假设在我的独立 cpu 上运行的工作必须特别请求它,例如使用schedule_on_each_cpu

【讨论】:

    猜你喜欢
    • 2021-07-18
    • 2011-07-23
    • 2017-09-09
    • 2011-07-27
    • 1970-01-01
    • 1970-01-01
    • 2014-02-20
    • 1970-01-01
    相关资源
    最近更新 更多