利用cpu特性优化高性能程序

在高性能、高并发程序编写过程中,如果合理利用cpu本身特性,能够大幅提供程序的性能,降低cpu占用。本文将介绍利用cache、numa特性,降低cpu占用的手段

在linux命令行中,执行lscpu,可以看到cpu的详细信息,如下所示:

Architecture:          x86_64                        //系统架构x86_64
CPU op-mode(s):        32-bit, 64-bit      //可以工作在32、64位环境下
Byte Order:            Little Endian              //小端字节序,ibm的powerpc是大端字节序
CPU(s):                24                                  //一个有24颗逻辑cpu
On-line CPU(s) list:   0-23                       //在线的cpu为0~24,使cpu下线的方法是echo 0 > /sys/devices/system/cpu/cpuX/online
Thread(s) per core:    2                            //超线程,每个cpu核运行2个线程
Core(s) per socket:    6                            //每个物理cpu包含6个核,即12个逻辑cpu
Socket(s):             2                                    //主板上2个cpu插槽,共有2个物理cpu
NUMA node(s):          2                              //有2个numa结点
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 45
Stepping:              7
CPU MHz:               2000.121                  //cpu主频2000MHz
BogoMIPS:              3999.47
Virtualization:        VT-x                            // 支持虚拟化
L1d cache:             32K                           //L1数据cache 32KB
L1i cache:             32K                             //L1指令cache 32KB 
L2 cache:              256K                           //L2 cache 256KB
L3 cache:              15360K                      //L3 cache 15MB
NUMA node0 CPU(s):     0,2,4,6,8,10,12,14,16,18,20,22   //node0 上的cpu序号
NUMA node1 CPU(s):     1,3,5,7,9,11,13,15,17,19,21,23



可以通过下图看出cpu内部的缓存分布:






在图中可以看出,每个socket即物理cpu,管理16GB的内存,并且每个物理cpu共享15MB的L3缓存。每个cpu核拥有单独的L1i、L1d、L2缓存,这个cpu核上的两个线程(超线程cpu核)共享这些缓存。

1. cache line

cpu cache line的概念可以参考:http://blog.csdn.net/zdl1016/article/details/8882092

cpu不会直接访问内存,都是通过将内存数据load到缓存中,再从cache间接获得数据。在64位的cpu上cache line的大小通常为64字节,也就是说每次cpu的cache会将64字节的数据从主存中加载到cache line中。但是,如果当程序的多个线程运行在不同的cpu核上,并且这几个线程要并发访问同一个全局结构体中的变量,就会导致SMP同步竞争、缓存失效等问题。

比如存在如下结构体

struct test{

    int readNum;

    int  writeNum;

};

struct test  testA;

在cpu0中,线程1对testA.readNum++,而在cpu1中,线程2对testA.writeNum++。线程1、线程2的每次操作,都会导致另外一个cpu核上对应的cache line无效,另外一个cpu需要重新从主存中将结构体load到cache中。

与之相比:

struct test{

    int readNum;

    char hole1[64];

    int writeNum;

};

struct test testB;

这次,由于结构体中存在一个64字节的空洞,所以cpu0加载到缓存中的数据中,是不含有writeNum的,也就是无论线程0对testB.readNum如何操作,也影响不到cpu1中的cache的有效性。从而避免了cache频繁地从主存中读取数据


2. 绑核

    对于cpu占用比较高的线程,可以使用绑核的手段将其与固定的cpu核绑定。由于进程在不同的核切换过程耗费cpu,并且,切换cpu也导致了之前cpu的cache中数据失效,在新的cpu核中,还要重新load数据的cache。所以对于性能要求很高的线程进行绑核,是非常必要的。

    绑核的函数如下所示:

void setThreadAffinity(long _id)
{
  long thread_id = (long)_id;
  u_int numCPU = sysconf( _SC_NPROCESSORS_ONLN );

  if(numCPU > 1) {

    int s;
    /* Bind this thread to a specific core */
    cpu_set_t cpuset;
    u_long core_id = thread_id % numCPU;

    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset);
    if((s = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset)) != 0)
      printf("Error while binding thread %ld to core %ld: errno=%i\n",
	     thread_id, core_id, s);
    else {
      printf("Set thread %lu on core %lu/%u\n", thread_id, core_id, numCPU);
    }
  }
}


3. 多线程并发,也要利用好cache

    在一般的概念里,多个线程跑在不同的cpu核上性能一定比跑在一个cpu核上性能要好,但是在实际运行环境中未必如此。

    比如说典型的缓存程序场景中,一个线程作为生产者,向缓存中put数据,另外一个线程作为消费者,从缓存中读取数据并通过socket发送给其他客户端。由于只有同一个cpu核(或者同一个核上的2个超线程核)能够使用32KB的L1d、256KB的L2缓存,所以当生产者线程、消费者线程的步调高度一致的时候,就能最大限度的利用L1、L2缓存。如果生产者、消费者线程耗费的cpu很多,如果共存在一个cpu上会导致cpu占率100%,也可以退而求其次,将这两个线程部署在同一个numa node上,也就是同一个物理cpu上,这样就可以最大限度的利用L3 cache。

    并且还要注意,很多情况下,cpu0、cpu1并不一定在一个物理cpu上,比如本文所示的图中,cpu 核0,2,4,6,8,10,12,14,16,18,20,22位于一个物理cpu中,cpu核1,3,5,7,9,11,13,15,17,19,21,23位于另外一个物理cpu中。


4. 利用好numa架构的特点

      通常较新的服务器cpu都是numa架构的,当一台服务器中,是使用了2个物理cpu以后,就要在编程中考虑numa架构带来的影响。在多个cpu的numa环境中,cpu访问的内存分为近端内存、远端内存。比如在两个cpu的服务中,每个物理cpu都是一个numa node,每个numa node都直接管理一部分内存,有本numa node管理的内存就被称为近端内存,当cpu访问这种内存的开销较小时延较低;远端内存是指另外一个numa node管理的内存,当cpu访问远端内存时,就要通过QPI总线访问另一cpu,由另一个cpu读取内存,所以访问远端内存的开销较大,cpu占用较高。

    为了解决这种问题,系统提供了一系列的numa api,通过numa api可以干预内存的分配,将线程所需的内存分配到近端内存区域。   最简单的用法是使用numa_setlocal_memory函数,这个函数的两个参数是内存的起始地址、长度,通过这个函数可以将这部分内存移动到近端内存区域,加速对这部分内存的访问。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值