您现在的位置是:首页 > 正文

Linux线程性能分析和CPU亲和力

2024-02-01 02:05:29阅读 2

一,线程迁移和负载均衡

        Linux系统在多核CPU和SMP系统上有完善的负载均衡支持。在SMP系统中,每个CPU的核都有一个迁移线程守护程序migration(一般是系统最高优先级139,实时99),以实现执行资源平衡作业。当我们调用sched_setaffinity系统调用将一个线程从源CPU核迁移到目标CPU核,并且该线程正在运行或处于TASK_WAKING状态,则迁移作业将打包到源CPU的迁移线程的工作队列中,然后,迁移线程将被唤醒以完成迁移工作。

        在对多核系统的进程或者线程进行性能分析时,有一些需要分析和注意的点,如:线程的优先级,线程对CPU核的占用情况,线程调度等等。

二,通过ps命令查看线程的调度和运行核

        在linux系统中,执行线程任务的核用P或者PSR表示,即Last Used CPU(SMP),表示最近一次执行线程的CPU core核。

1,ps -eLF

         下面的命令可以持续监测某一个核调度运行的所有线程,本实例为排序显示核5调度的所有线程。

while true; do ps -eLF | awk '{print $2,$3,$4,$9}' | sort -n -k 4 | grep " 5$"; done

 2,ps命令指定查看某些特定的项

ps H -eotid,pid,ppid,pri,rtprio,psr,state,%cpu,%mem,cmd

  • 备注,STATE(S):代表该进程目前的状态,主要的状态有:
    • R:该进程正在运行
    • S:该进程正在休眠,但可被某些信号(signal)唤醒
    • D:无法中断的休眠状态(通常为IO进程)
    • T:该进程已经停止
    • Z:僵死状态,该进程应该已经终止,但是其父进程却无法正常的终止它,造成zombie(疆尸)程序的状态
    • W:等待状态,等待内存的分配
    • <:高优先级的进程
    • N:低优先级的进程

三,通过top命令查看线程的调度和运行核

        top H -d 1 [-p pid]

        通过top命令查看线程的(调度)信息,也可以通过-p指定进程或者线程号查看某一个进程的所有线程,或者某个线程的信息。

        下图是在查看KVM虚拟机性能分析时的一个实例:top H -d 1 -p 10166

        在top命令里面,有几个小的命令说明一下以备忘

        1)查看所有核的CPU使用情况:在top界面上按数字“1”;再次按“1”可以收起

        2)高亮运行的线程:在top界面上按字母“b”会高亮R状态的线程。

        3)top字段管理。在top界面上按字母“f”,会调出字段管理界面。上下箭头可以移动,空格键可以用于选择或者取消字段,注意一定是空格键而不是回车键进行选择和取消,已经选中显示的列会变为黑体。

        对调度和性能分析需要关注的几个主要指标:

                P :查看线程被调用的CPU核             

                PR:线程优先级

                %CPU:线程占用CPU的实时情况     

                %MEM:内存占用情况,同一个进程内的线程一样

                WCHAN:线程睡眠所在的函数,一般都是内核的函数

        4)按某一字段排序:按“f”进入字段管理界面后,先用上下键移到要排序的字段位置,然后按字母“s”,然后“q”退回到top的主界面,此时看到的就是排序后的显示。

       在top命令行也可以通过-o选定排序的字段。

       如 top H -d 1 -o COMMAND,需要注意大小写。

       排序有一些快捷键,如“N”对线程排序,“M”对内存排序,“P”对CPU排序。“R”反序。

       排序对于分析一些性能问题其实非常重要,可以聚焦在某些线程上。

       通用排序方法:可以在top主界面下按字母"x",会高亮显示当前排序的列。x和b可以联合使用。 

四、pidstat周期查看进程/线程使用cpu情况

        pidstat命令可以周期查看(最小周期1秒)一个或者多个线程(或者进程)的CPU使用情况。如果线程在多个核上调度(或者绑定在多个CPU核上),多个cpu核都会都显示出来,可以显示每个线程在用户空间,系统空间分别占用的CPU百分比,以及在哪个核上运行。

       显示多个线程用“,”隔开,命令:pidstat -p 15380,13000 -t 1 

五、CPU亲和性(亲和力 CPU Affinity)

        有了前面的感性认识,现在终于来到重点,在SMP多处理系统中CPU亲和力。

        CPU Affinity 是一种调度属性(scheduler property),是指进程(或线程)在某个给定CPU核上尽量长时间运行而不被迁移到其他核的倾向性。它可以将一个线程(进程)"绑定" 到一个或一组CPU核上。在SMP(Symmetric Multi-Processing对称多处理)架构下,Linux调度器(scheduler)会根据CPU affinity的设置让指定的进程运行在"绑定"的CPU上,而不会在别的CPU上运行。

        在多核计算机中,每个CPU核都有自己的缓存,一旦线程(或进程)被操作系统调度到其他核上,整个缓存都需要重建,缓存的命中率降低,系统性能下降。在对延迟敏感的系统中,合理地分配线程(或进程)与CPU的亲和性,能够有效提升系统性能,降低延迟。

        CPU亲和性可以分为软亲和性硬亲和性

软亲和性

        线程(或进程)保持在一个CPU核上持续运行而尽量不被迁移到其他核。但是在如负载过重等不得已的情况下,依然会执行迁移操作。

        Linux内核的进程调度器天然就支持软亲和性。所以无需做额外配置。

硬亲和性

        将线程(或进程)强行绑定在某个核上运行,不允许被调度到其他核上。

        如果想让特定进程或线程独占某一或某些CPU,需要做三件事:

  • 隔离CPU核

        硬亲和性支持首先需要将核隔离出来,被隔离出来的核不会再被操作系统的调度器使用,也就是说其他的CPU就算再忙,隔离出来的CPU也是空闲的。这样可以避免其它线程运行在被隔离的CPU上,只有你的程序会跑在这个(些)指定的CPU核上,并且没有其他任务会来抢占你的CPU。,这些被移除的CPU称为"isolated" CPU。

        被隔离的CPU虽然Linux调度器不会让线程run在上面,但是仍会收到interrupt!对于性能和延迟要求高的线程,还需要做中断隔离。

        隔离CPU核需要在Linux系统启动前指定,打开/etc/default/grub,修改参数GRUB_CMDLINE_LINUX,增加isolcpus=0(假设我们仅仅隔离CPU的第0核),如果要隔离多个核,只需要在核间用逗号","隔开即可。如我们隔离20,29-31四个核,那么增加isolcpus=20,29,30,31或者isolcpus=20,29-31。执行update-grub让配置生效,重启系统后这些核就不会被Linux系统的调度器调度了。

  • 绑定CPU核

       硬亲和性支持还需要将用户的线程(进程)绑定在隔离的核上,使其不会被Linux系统调度到其他空闲的核上而造成CPU上下文切换,导致程序性能下降。

        绑定CPU核有两种方式,一种是用CPU命令taskset,另外一种就是变成程序时通过API sched_setaffinity(sched_getaffinity)在写程序时指定。

        taskset

# 命令行形式
taskset [options] mask command [arg]...
taskset [options] -p [mask] pid

PARAMETER
  mask : cpu亲和性,当没有-c选项时, 其值前无论有没有0x标记都是16进制的,当有-c选项时,其值是十进制的。
  command : 命令或者可执行程序
  arg : command的参数
  pid : 进程ID,可以通过ps/top/pidof等命令获取

OPTIONS
  -a, --all-tasks (旧版本中没有这个选项)
    这个选项涉及到了linux中TID的概念,他会将一个进程中所有的TID都执行一次CPU亲和性设置,TID就是Thread ID,他和POSIX中pthread_t表示的线程ID完全不是同一个东西。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),这个TID就是这个线程的真实PID。
       -p, --pid
              操作已存在的PID,而不是加载一个新的程序
       -c, --cpu-list
              声明CPU的亲和力使用数字表示而不是用位掩码表示. 例如 0,5,7,9-11.
       -h, --help
              display usage information and exit
       -V, --version
              output version information and exit
  USAGE

  1) 使用指定的CPU亲和性运行一个新程序

    taskset [-c] mask command [arg]...

                 使用CPU0运行ls命令显示/etc/init.d下的所有内容 
       taskset -c 0 ls -al /etc/init.d/

  2) 显示已经运行的进程的CPU亲和性
        taskset -p pid
          [root@localhost ~]# taskset -cp 1393
          pid 1393's current affinity list: 0-7
  3) 改变已经运行进程的CPU亲和力
      taskset -p[c] mask pid
        更改具体某一进程(或 线程)CPU亲和性
          taskset  -p   hexadecimal mask  PID/LWP
          上面1393号线程可以在0~7号CPU之间允许,现在设置掩码0x11(二进制0001 0001),表示可以在0~4号CPU上允许。
          [root@localhost ~]# taskset -p 0x11  1393
          pid 1393's current affinity mask: ff
          pid 1393's new affinity mask: 11
          [root@localhost ~]# taskset -p   1393
          pid 1393's current affinity mask: 11
          [root@localhost ~]# taskset -cp   1393
          pid 1393's current affinity list: 0,4

            为具体某一进程(或 线程)CPU亲和性指定一组范围
              使用-c参数
              [root@localhost ~]# taskset -cp 20,29-31  1393
              pid 1393's current affinity list: 0,4
              pid 1393's new affinity list: 20,29-31
              [root@localhost ~]# taskset -cp   1393
              pid 1393's current affinity list: 20,29-31

  PERMISSIONS
        一个用户要设定一个进程的CPU亲和性,如果目标进程是该用户的,则可以设置,如果是其他用户的,则会设置失败,提示 Operation not permitted.当然root用户没有任何限制.
        任何用户都可以获取任意一个进程的CPU亲和性。

        sched_setaffinity(sched_getaffinity)

        常用的C的代码段如下:

#include <sched.h>

    cpu_set_t mask;               // cpu核的位掩码
    CPU_ZERO(&mask);       // 将CPU核列表置空
    CPU_SET(20, &mask);    // 将需要绑定的cpu号设置在mask中
    CPU_SET(29, &mask);    // 将需要绑定的cpu号设置在mask中
    CPU_SET(30, &mask);    // 将需要绑定的cpu号设置在mask中
    CPU_SET(31, &mask);    // 将需要绑定的cpu号设置在mask中
    if (sched_setaffinity(pthread_self(), sizeof(mask), &mask) == -1) // CPU绑定
    {
        printf("failed to set affinity.\n");
    }
    else
    {
        printf("succeeded to set affinity.\n");
    }

  • 中断隔离

        绑定所有的interrupts到非隔离的CPU上,避免被隔离的CPU收到interrupt而被消耗,造成绑定在其上的线程的调度。

        IRQ(Interrupt request)是硬件级别的服务请求,IRQ都有一个亲和度属性smp_affinity,smp_affinity决定允许哪些CPU核心处理该IRQ。当前Linux的某一特定IRQ的亲和度值储存在/proc/irq/IRP_NUMBER/smp_affinity文件中,只有ROOT权限用户可见和可操作。该值是一个十六进制位掩码(hexadecimal bit-mask),代表着系统的所有CPU核,跟taskset和CPU_SET一样,每一个bit对应一个CPU核。

        命令cat /proc/interrupts可以看到所有设备的interrupts信息,第一列即为IRP_NUMBER。

        命令cat /proc/irq/32/smp_affinity可以看到IRQ号为32的亲和度,默认值为f(跟CPU的核数紧密相关,此处f表示4核CPU),代表这个IRP能被所有CPU接受处理。

        命令echo 1 >/proc/irq/32/smp_affinity把IRQ号为32的亲和度值设为1,代表这个IRP仅能被CPU0接受处理。echo 1fefffff > /proc/irq/32/smp_affinity,表示32号中断不送到CPU的20,29-31核。

        其他类推,我们可以据要求任意绑定IRQ到CPU。不过,系统中仍有一部分中断没有被绑定,例如:Single function call interrupts, Local timer interrupts等等。

六、结语

        对于性能和延迟要求高的线程,以及如何提升CPU资源紧张的系统,本文仅从Linux系统的调度方面进行了简单的描述,通过上述的方法和一些原则进行调整和优化,也许可以逐步使系统和线程达到最佳的平衡。当然线程优先级的合理分配,系统资源的平衡调度,对于性能要求高的系统的整体性能,也有很大的影响!

网站文章