linux之CPU

问题

  • CPU的规格一样吗?
  • 多核 CPU 和多个 CPU 有何区别?
  • CPU线程切换时,CPU缓存中的数据如何变化?

CPU概述

cpu作用

CPU只执行三种基本的操作,分别是读出数据、处理数据和往内存写数据。

cpu规格

主流CPU还是Intel和AMD两家的天下。无论是高端还是低端,两大品牌都有着全线的产品。

Intel和AMD的规格是不同的,根据根据主板的cpu接口选择适当的cpu,值得一提的是,同样的Intel,不同系列的cpu,规格也不同,如i3~9与至强系列

系列 尺寸 接口
i3~i9 37.5×37.5mm LGA 1200
至强 45×52.5mm LGA 2011-3

数据来源 中关村在线

cpu频率

时钟周期

在计算机中,时钟发生器不停给芯片发送一个连续的脉冲信号,每一次脉冲到来,芯片内的晶体管就改变一次状态,让整个芯片完成一定任务。

电脑中有许许多多的半导体芯片,这些芯片就是在时钟发生器发送的特定时钟频率下有条不紊进行工作的。

如图,时钟发生器发出的脉冲信号做出周期变化的最短时间称之为震荡周期,也称为 CPU 时钟周期。它是计算机中最基本的、最小的时间单位

主频

衡量CPU速度快慢的一个重要指标就是CPU的工作频率,也叫做CPU的主频,主频亦称为内频。主频就是CPU的时钟频率,它控制着CPU工作节拍,主频越高,CPU工作节拍就越快,运算速度也就越高。主频通常用一秒钟内处理器所能发出电子脉冲数来测定,计量单位一般为MHz或GHz。

每一次脉冲(即一个震荡周期)到来,芯片内的晶体管就改变一次状态,让整个芯片完成一定任务。一个震荡周期内,晶体管只会改变一次状态。由此,更小的时钟周期就意味着更高的工作频率。

一秒(1 s)内,震荡周期的个数称为时钟频率,俗称主频。

不难看出主频和时钟周期的关系:

时钟频率(主频)= 1/CPU时钟周期

由上面的关系不难推出,主频越高,CPU的运算速度就越快。

每一款芯片都有自己的频率极限。因为电脑中的芯片绝大多数属于数字逻辑芯片,数字芯片中众多的晶体管全都工作在开关状态,它们的导通和关断动作无不是按照时钟信号的节奏进行的。如果时钟频率过高,就可能出现晶体管的状态来不及变化的情况,产生死锁或随机性误操作。

主频越高,CPU的运算速度就越快。但主频不等于处理器一秒钟执行的指令条数,因为一条指令的执行可能需要多个时钟周期。

如intel i3主频3.60 GHz(1GHZ=10^3MHZ=10^6KHZ= 10^9HZ)表示该cpu在1秒钟时间内发送3.6G个脉冲,即晶体管调整3.6G次状态

外频

外频是CPU乃至整个计算机系统的基准频率,单位是MHz(兆赫兹)

CPU跟外部(即系统总线)接触沟通的频率称为外频。外频是由主板提供,CPU以这个频率跟系统其他的配件进行沟通,因此,外频亦称为系统总线频率或前端总线速度(FSB)。早期CPU内部与外部的工作频率都相同,后来主频要比外频快。

倍频

CPU的倍频,即倍频系数。它指CPU主频和外频之间存在着一个比值关系,这个比值就是倍频系数。所以,主频和外频、倍频三者的关系是:主频=外频×倍频

超频

有的 CPU 芯片允许在短时间内提升时钟频率,即俗称的“超频”,以获得更好的性能。当然,如果长时间处于超频状态,将可能对芯片造成 irreparable 损伤。

cpu结构

运算单元

运算单元只管算,例如做加法、做位移等等。但是,它不知道应该算哪些数据,运算结果应该放在哪里

数据单元

数据单元包括 CPU 内部的缓存和寄存器组,空间很小,但是速度飞快,可以暂时存放数据和运算结果。

控制单元

控制单元是一个统一的指挥中心,它可以获得下一条指令,然后执行这条指令。这个指令会指导运算单元取出数据单元中的某几个数据,计算出个结果,然后放在数据单元的某个地方。

缓存(cache)

缓存不属于CPU的核心功能,是cpu为了提升效率添加的内存缓存,分为一级缓存 二级缓存 和 三级缓存。其中三级缓存是不同核心共享的。通过缓存中使用的是内存物理地址,而非虚拟地址,数据在缓存中的存储方式为“内存物理址,值;内存物理地址,值”

多核心CPU

不同CPU运行多个线程时,可能需要访问同一内存地址的数据。如 CPU0访问某地址数据时,可能该数据已经被CPU1加载到缓存中并进行了修改。

解决该问题有两种方式:

  • CPU1修改数据后将缓存数据同步至内存后,CPU0再从内存中读取数据
  • CPU0直接访问CPU2的缓存

由于上述两种方案中CPU均需要读取内存,故多个cpu时性能较差,因此通常采用性能更好的一个CPU中多个核心的方式解决上述问题,多个核心共享CPU三级缓存。

而多个CPU通常用于堆出更多的内核的情况。如一个服务器可能是4个10核心CPU,即40核。

假如一个4 core单CPU的服务器,在系统层面使用感知到的是4个CPU

超线程

intel的cpu拥有超线程技术,可以为一个逻辑核心开启两个处理线程。通过查看物理CPU数,每个CPU的逻辑核数,CPU线程数(即总Processor数量)可以得知是否开启了超线程。

cpu缓存

cpu缓存概述

CPU 缓存离 CPU 核心更近,由于电子信号传输是需要时间的,所以离 CPU 核心越近,缓存的读写速度就越快。但 CPU 的空间很狭小,离 CPU 越近缓存大小受到的限制也越大。所以,综合硬件布局、性能等因素,CPU 缓存通常分为大小不等的三级缓存。

CPU 缓存的材质 SRAM 比内存使用的 DRAM 贵许多,所以不同于内存动辄以 GB 计算,它的大小是以 MB 来计算的。

linux查看CPU缓存

[root@centos75_100-57 index0]# cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K
[root@centos75_100-57 index0]# cat /sys/devices/system/cpu/cpu0/cache/index1/size
32K
[root@centos75_100-57 index0]# cat /sys/devices/system/cpu/cpu0/cache/index2/size
256K
[root@centos75_100-57 index0]# cat /sys/devices/system/cpu/cpu0/cache/index3/size
30720K
  • 其中cpu0表示为哪个cpu,如果当前宿主机为4核,则会存在cpu0~cpu3共四个目录
  • index0/index1为一级缓存
  • index2为二级缓存,index3为三级缓存
  • 三级缓存要比一、二级缓存大许多倍,这是因为当下的 CPU 都是多核心的,每个核心都有自己的一、二级缓存,但三级缓存却是一颗 CPU 上所有核心共享的

CPU数据访问过程

程序执行时,会先将内存中的数据载入到共享的三级缓存中,再进入每颗核心独有的二级缓存,最后进入最快的一级缓存,之后才会被 CPU 使用

缓存要比内存快很多。CPU 访问一次内存通常需要 100 个时钟周期以上,而访问一级缓存只需要 4~5 个时钟周期,二级缓存大约 12 个时钟周期,三级缓存大约 30 个时钟周期(对于 2GHZ 主频的 CPU 来说,一个时钟周期是 0.5 纳秒.

三级缓存

CPU三级缓存,就是指CPU的第三层级的高速缓存,其作用是进一步降低内存的延迟,同时提升海量数据量计算时的性能。和一级缓存、二级缓存不同的是,三级缓存是核心共享的,能够将容量做的很大。

二级缓存

CPU二级缓存,就是指CPU的第二层级的高速缓存,而二级缓存的容量会直接影响到CPU的性能,二级缓存的容量越大越好。例如intel的第八代i7-8700处理器,共有六个核心数量,而每个核心都拥有256KB的二级缓存,属于各核心独享,这样二级缓存总数就达到了1.5MB。

一级缓存

CPU一级缓存,就是指CPU的第一层级的高速缓存,主要当担的工作是缓存指令和缓存数据。一级缓存的容量与结构对CPU性能影响十分大,但是由于它的结构比较复杂,又考虑到成本等因素,一般来说,CPU的一级缓存较小,通常CPU的一级缓存也就能做到256KB左右的水平。

CPU 缓存中有 2 个一级缓存(比如 Linux 上就是上述中的 index0 和 index1),这是因为,CPU 会区别对待指令与数据。比如,“1+1=2”这个运算,“+”就是指令,会放在一级指令缓存中,而“1”这个输入数字,则放在一级数据缓存中。虽然在冯诺依曼计算机体系结构中,代码指令与数据是放在一起的,但执行时却是分开进入指令缓存与数据缓存的,因此我们要分开来看二者的缓存命中率。

切换线程时是否需要清除缓存

线程切换时CPU缓存中对应的数据是否会被清除取决于CPU缓存使用的是虚拟地址还是物理地址

  • 如果使用的是物理地址:因为线程间的物理地址基本是没有重叠的,如果有重叠,也是有意实现的内存共享,所以线程切换时没有必要清除cache。
  • 如果使用的是虚拟地址:如果下一个线程和当前线程属于同一进程,那么它们的虚拟地址空间是相同的,就不必清除cache;否则,来自不同进程的两个线程使用不同的虚拟空间映射,还有可能不同的cachline对应的是同一块物理内存(别名问题),切换线程就需要清除cache。

intel的cpu中缓存都是用的物理地址,所以线程切换时不需要特意清空cache。

而线程切换时,是需要切换寄存器的,具体如下:

  • 进程的上下文:用户级上下文、寄存器上下文以及系统级上下文。
    • (1)用户级上下文: 正文、数据、用户堆栈以及共享存储区;
    • (2)寄存器上下文: 通用寄存器、程序寄存器(EIP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
    • (3)系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。
  • 进程切换需要切换(1)(2)(3)
  • 用户态和内核态的切换只需要(2)
  • 线程的切换也只需要(2)

cache line

每次内存和CPU缓存之间交换数据都是固定大小,cache line定义了cpu缓存一次载入数据的大小,Linux 上你可以通过 coherency_line_size 配置查看它,通常是 64 字节。

[root@k8s-master index0]# cat /sys/devices/system/cpu/cpu1/cache/index0/coherency_line_size
64

每次缓存的cache line的结构:

  • tag:包含部分内存地址
  • data block:就是一个cache line的内容
  • flag bits:一般包含数据是否有效(valid bit)和数据是否被写(dirty bit),指令缓存因为只读

缓存命中率

如果 CPU 所要操作的数据在缓存中,则直接读取,这称为缓存命中。命中缓存会带来很大的性能提升,因此,我们的代码优化目标是提升 CPU 缓存的命中率.

数据缓存命中率

按照内存布局顺序访问将会带来很大的性能提升

当对二维数据进行遍历时,使用array[i][j]比array[j][i]性能要好

for(i = 0; i < N; i+=1) {
   for(j = 0; j < N; j+=1) {
       array[i][j] = 0;
   }
}

当访问 array[0][0]时,缓存已经把紧随其后的 3 个元素也载入了,CPU 通过快速的缓存来读取后续 3 个元素就可以。

[kevin@kevin ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size 
64

上述服务器一次会载入 64 字节至缓存中。当载入 array[0][0]时,若它们占用的内存不足 64 字节,CPU 就会顺序地补足后续元素。顺序访问的 array[i][j]因为利用了这一特点,所以就会比 array[j][i]要快。所以使用元素类型是 4 个字节的整数,性能会比 8 个字节的高精度浮点数时速度更快,因为缓存一次载入的元素会更多

上述实验中,访问array[i][j] 比array[j][i]的性能大概优8倍,原因为在二维数组中,其实第一维元素存放的是地址,第二维存放的才是目标元素。由于 64 位操作系统的地址占用 8 个字节(32 位操作系统是 4 个字节),因此,每批 Cache Line 最多也就能载入不到 8 个二维数组元素,所以性能差距大约接近 8 倍

指令缓存的命中率

有一个元素为 0 到 255 之间随机数字组成的数组。

假如对该数据进行如下操作:

  • 一是循环遍历数组,判断每个数字是否小于 128,如果小于则把元素的值置为 0;
  • 二是将数组排序。
for(i = 0; i < N; i++) {
       if (array [i] < 128) array[i] = 0;
}
sort(array, array +N);

那么,先排序再遍历速度快,还是先遍历再排序速度快呢?

答案是先排序的遍历时间只有后排序的三分之一。这是因为循环中有大量的 if 条件分支,而 CPU含有分支预测器。

当代码中出现 if、switch 等语句时,意味着此时至少可以选择跳转到两段不同的指令去执行。如果分支预测器可以预测接下来要在哪段代码执行(比如 if 还是 else 中的指令),就可以提前把这些指令放在缓存中,CPU 执行时就会很快。当数组中的元素完全随机时,分支预测器无法有效工作,而当 array 数组有序时,分支预测器会动态地根据历史命中数据对未来进行预测,命中率就会非常高

多核 CPU 下的缓存命中率

前面都是面向一个 CPU 核心中数据及指令缓存的,然而现代 CPU 几乎都是多核的。虽然三级缓存面向所有核心,但一、二级缓存是每颗核心独享的。我们知道,即使只有一个 CPU 核心,现代分时操作系统都支持许多进程同时运行。这是因为操作系统把时间切成了许多片,微观上各进程按时间片交替地占用 CPU,这造成宏观上看起来各程序同时在执行。

因此,若进程 A 在时间片 1 里使用 CPU 核心 1,自然也填满了核心 1 的一、二级缓存,当时间片 1 结束后,操作系统会让进程 A 让出 CPU,基于效率并兼顾公平的策略重新调度 CPU 核心 1,以防止某些进程饿死。如果此时 CPU 核心 1 繁忙,而 CPU 核心 2 空闲,则进程 A 很可能会被调度到 CPU 核心 2 上运行,这样,即使我们对代码优化得再好,也只能在一个时间片内高效地使用 CPU 一、二级缓存了,下一个时间片便面临着缓存效率的问题。

因此,操作系统提供了将进程或者线程绑定到某一颗 CPU 上运行的能力。如 Linux 上提供了 sched_setaffinity 方法实现这一功能,其他操作系统也有类似功能的 API 可用。当多线程同时执行密集计算,且 CPU 缓存命中率很高时,如果将每个线程分别绑定在不同的 CPU 核心上,性能便会获得非常可观的提升。

linux cpu操作

查看服务器cpu信息

processor	: 0
cpu MHz		: 2194.918
cache size	: 6144 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 2
clflush size	: 64
cache_alignment	: 64
address sizes	: 39 bits physical, 48 bits virtual

processor	: 1
cpu MHz		: 2194.918
cache size	: 6144 KB
physical id	: 0
siblings	: 2
core id		: 1
cpu cores	: 2
clflush size	: 64
cache_alignment	: 64
address sizes	: 39 bits physical, 48 bits virtual
  • 根据上述physical id,发现两个cpu的数据id均为0表示只有一个cpu
  • cpu cores:2表示当前cpu为两核cpu
  • 由于上述共两个processor 故为两核两线程cpu

关闭某个cpu

[root@k8s-master cpu1]# echo 0 > /sys/devices/system/cpu/cpu1/online
[root@k8s-master cpu1]# cat /proc/cpuinfo
processor	: 0
cpu MHz		: 2194.918
cache size	: 6144 KB
physical id	: 0
siblings	: 1
core id		: 0
cpu cores	: 1
clflush size	: 64
cache_alignment	: 64
address sizes	: 39 bits physical, 48 bits virtual
  • 向某个cpu下的online文件中写入0即关闭该cpu,当写入1时即开启该cpu,重启后失效
  • /sys/devices/system/cpu/online文件内容为当前所有开启的cpu
  • /sys/devices/system/cpu/offline文件内容为当前所有关闭的cpu
  • 部分cpu下无online文件,即无法关闭,即使root账户也无权限新增online文件

共享核心的超线程

我们可以通过$cpu/topology/thread_siblings_list查看与当前processor共享核心的所有processor

如果thread_siblings_list中第一个数字等于CPU id(即1==1)那么它就是一个真正的核心,如果不是超线程核心。

# cat /sys/devices/system/cpu/cpu1/topology/thread_siblings_list
1,13

如果第一个数字不等于cpu id(即13!=1)则为超线程核心的例子

# cat /sys/devices/system/cpu/cpu13/topology/thread_siblings_list
1,13

如果thread_siblings_list中只有一个数字,则表示该核心为单线程

我们可以通过附1脚本可以关闭或开启超线程

puX/topology/thread_siblings内容为cpuX所在core的逻辑处理器的列表(用于内核内部)
cpuX/topology/thread_siblings_list内容为cpuX所在core的逻辑处理器的列表(用于人可读的)

工具

Perf

Perf 工具可以直观地验证缓存命中的情况

  • 执行 perf stat 可以统计出进程运行时的系统信息(通过 -e 选项指定要统计的事件,如果要查看三级缓存总的命中率,可以指定缓存未命中 cache-misses 事件,以及读取缓存次数 cache-references 事件,两者相除就是缓存的未命中率,用 1 相减就是命中率。类似的,通过 L1-dcache-load-misses 和 L1-dcache-loads 可以得到 L1 缓存的命中率),此时你会发现 array[i][j]的缓存命中率远高于 array[j][i]
  • 查看数据缓存命中情况
  • 查看分支预测情况
  • 进程从不同的 CPU 核心上迁移的次数

Gpu

图形处理器(英语:Graphics Processing Unit,缩写:GPU),又称显示核心、视觉处理器、显示芯片,是一种专门在个人电脑、工作站、游戏机和一些移动设备(如平板电脑、智能手机等)上做图像和图形相关运算工作的微处理器。

GPU使显卡减少了对CPU的依赖,并进行部分原本CPU的工作,尤其是在3D图形处理时GPU所采用的核心技术有硬件T&L(几何转换和光照处理)、立方环境材质贴图和顶点混合、纹理压缩和凹凸映射贴图、双重纹理四像素256位渲染引擎等,而硬件T&L技术可以说是GPU的标志。GPU的生产商主要有NVIDIA和ATI。

附1

#!/bin/bash

HYPERTHREADING=1

function toggleHyperThreading() {
  for CPU in /sys/devices/system/cpu/cpu[0-9]*; do
      CPUID=`basename $CPU | cut -b4-`
      echo -en "CPU: $CPUID\t"
      [ -e $CPU/online ] && echo "1" > $CPU/online
      THREAD1=`cat $CPU/topology/thread_siblings_list | cut -f1 -d,`
      if [ $CPUID = $THREAD1 ]; then
          echo "-> enable"
	  echo "$CPU"
          [ -e $CPU/online ] && echo "1" > $CPU/online
      else
        if [ "$HYPERTHREADING" -eq "0" ]; then echo "-> disabled"; else echo "-> enabled"; fi
          echo "$HYPERTHREADING" > $CPU/online
      fi
  done
}

function enabled() {
        echo -en "Enabling HyperThreading\n"
        HYPERTHREADING=1
        toggleHyperThreading
}

function disabled() {
        echo -en "Disabling HyperThreading\n"
        HYPERTHREADING=0
        toggleHyperThreading
}

#
ONLINE=$(cat /sys/devices/system/cpu/online)
OFFLINE=$(cat /sys/devices/system/cpu/offline)
echo "---------------------------------------------------"
echo -en "CPU's online: $ONLINE\t CPU's offline: $OFFLINE\n"
echo "---------------------------------------------------"
while true; do
    read -p "Type in e to enable or d disable hyperThreading or q to quit [e/d/q] ?" ed
    case $ed in
        [Ee]* ) enabled; break;;
        [Dd]* ) disabled;exit;;
        [Qq]* ) exit;;
        * ) echo "Please answer e for enable or d for disable hyperThreading.";;
    esac
done

参考