lib不是有效的win32应用程序_鲲鹏性能优化十板斧 | 应用程序调优

《鲲鹏性能优化十板斧》系列介绍了针对鲲鹏服务器的应用程序调优策略。本文重点讲解如何解决'lib不是有效的win32应用程序'问题,并提出优化编译选项、文件缓冲、执行结果缓存、内存拷贝、锁优化、内存分配等方法提升程序性能。
摘要由CSDN通过智能技术生成

ba8b525b3c85ad52c3f260930061632d.png

6f48013c25f86c00b0f695fa8feadb9b.gif

《鲲鹏性能优化十板斧系列文章(已完结)

(点击可跳转查看)

1.鲲鹏性能优化十板斧之前言 | 鲲鹏处理器NUMA简介与性能调优五步法

2.鲲鹏性能优化十板斧 | CPU与内存子系统性能调优

3.鲲鹏性能优化十板斧 | 网络子系统性能调优

4.鲲鹏性能优化十板斧 | 磁盘IO子系统性能调优

5.鲲鹏性能优化十板斧 | 应用程序调优(本文)

34f7d0d26bb1d7c1a0b91ef86ad6d2b2.png

1.调优简介

应用程序部署到鲲鹏服务器上以后,需要结合芯片和服务器的特点优化代码性能,使硬件能力得到充分发挥。本章列举几个典型场景,涉及锁、编译器配置、Cacheline、缓冲机制等优化。

34f7d0d26bb1d7c1a0b91ef86ad6d2b2.png

2 优化方法

2.1优化编译选项,提升程序性能

原理

C/C++代码在编译时,gcc编译器将源码翻译成CPU可识别的指令序列,写入可执行程序的二进制文件中。CPU在执行指令时,通常采用流水线的方式并行执行指令,以提高性能,因此指令执行顺序的编排将对流水线执行效率有很大影响。通常在指令流水线中要考虑:执行指令计算的硬件资源数量、不同指令的执行周期、指令间的数据依赖等等因素。我们可以通过通知编译器,程序所运行的目标平台(CPU)指令集、流水线,来获取更好的指令序列编排。在gcc 9.1.0版本,支持了鲲鹏处理器所兼容的armv8指令集、tsv110流水线。

修改方式

l  在Euler系统中使用HCC编译器,可以在CFLAGS和CPPFLAGS里面增加编译选项:

-mtune=tsv110 -march=armv8-a

l  在其它操作系统中,可以升级GCC版本到9.10,并在CFLAGS和CPPFLAGS里面增加编译选项:

-mtune=tsv110 -march=armv8-a

2.2文件缓冲机制选择

原理

内存访问速度要高于磁盘,应用程序在读写磁盘时,通常会经过一些缓存,以减少对磁盘的直接访问,如下图所示:

bbc70e4df68057312abf45cfc1c8a20f.png

clibbuffer:clib buffer是用户态的一种数据缓冲机制,在启用clib buffer的情况下,数据从应用程序的buffer拷贝至clib buffer后,并不会立即将数据同步到内核,而是缓冲到一定规模或者主动触发的情况下,才会同步到内核;当查询数据时,会优先从clib buffer查询数据。这个机制能减少用户态和内核态的切换(用户态切换内核态占用一定资源)。

PageCache:PageCache是内核态的一种文件缓存机制,用户在读写文件时,先操作PageCache,内核根据调度机制或者被应用程序主动触发时,会将数据同步到磁盘。PageCache机制能减少磁盘访问。

修改方式

应用程序根据自己的业务特点选择合适的文件读写方式:

l  fread/fwrite函数使用了clib buffer缓存机制,而read/write并没有使用,因此fread/fwrite比read/write多一层内存拷贝,即从应用程序buffer至clib buffer的拷贝,但是fread/fwrite比read/write有更少的系统调用。因此对于每次读写字节数较大的操作,内存拷贝比系统调用占用更多资源,可以使用read/write来减少内存拷贝;对于每次读写字节数较少的操作,系统调用比内存拷贝占用更多资源,建议使用fread/ fwrite来减少系统调用次数。

l  O_DIRECT模式没有使用PageCache,因此少了一层内存拷贝,但是因为没有缓冲导致每次都是从磁盘里面读取数据。

O_DIRECT主要适用场景为:应用程序有自己的缓冲机制;数据读写一次后,后面不再从磁盘读这个数据。

2.3执行结果缓存

原理

对于相同的输入,应用软件经过计算后,有相同的输出,可以将运算结果保存,在下次有相同的输入时,返回上次执行的结果。

修改方式

目前部分开源软件已经实现这种机制,举例如下:

1. Nginx缓冲

基于局部性原理,Nginx使用proxy_cache_path等参数将请求过的内容在本地内存建立一个副本,这样对于缓存中的文件不用去后端服务器去取。

2. JIT编译

JIT(Just-In-Time)编译,将输入文件转为机器码。为了提升效率,转换后的机器码被缓存在内存,这样相同的输入(如JAVA程序的字节码)不用重新翻译,直接返回缓存中的内容。如果发现JAVA虚拟机的C1Compiler/C2 Compiler线程的CPU占用比较多,可能是JIT缓冲不够,可以增加JAVA虚拟机ReservedCodeCacheSize参数。

3. MySql查询缓冲

MySQL的SQL语句缓存在内存中保存查询返回的结果。当查询命中时,直接返回解析后的SQL,跳过了查询操作的解析,优化和执行阶段。如果缓存的表被修改了,对应表的缓存就会失效。

MySql查询缓冲状态可以通过如下语句查询:

showstatus like ‘%Qcache%’;

MySql可以通过query_cache_size和query_cache_type来设置查询缓存的属性。

2.4减少内存拷贝

原理

基于数据流分析,发现并减少内存拷贝次数,能降低CPU使用率,并减少内存带宽占用。

修改方式

减少内存拷贝要基于业务逻辑进行分析,这里例举几种减少内存拷贝的实现机制:

l  样例一:使用sendfile代替send/sendto/write等函数将文件发送给对端

如下两条语句将文件发送给对端,一般会有4次内存拷贝

ssize_tread (int fd, void *buf, size_t count);

ssize_tsend (int s, const void * buf, size_t len, int flags);

− read函数一般有2次内存拷贝: DMA将数据搬运到内核的PageCache;内核将数据搬运到应用态的buf。

− send函数一般有2次内存拷贝: write函数将应用态的buf的拷贝到内核;DMA将数据搬运到网卡。

使用如下函数实现只需要两次内存拷贝:

ssize_tsendfile(int out_fd, int in_fd, off_t *offset, size_t count);

内核通过 DMA将文件搬运到缓存(一次内存拷贝),然后把缓存的描述信息(位置和长度)传递给TCP/IP协议栈,内核在通过DMA将缓冲搬运到网卡(第二次内存拷贝)。

除了修改代码,部分开源软件已经支持这个特性,如Nginx可以通过sendfileon参数打开这个功能。

l  样例二:进程间通信使用共享内存代替socket/pipe通信

内存共享方式可以让多个进程操作同样的内存区域,相比socket通信的的方式,内存拷贝少。应用程序可以使用shmget等函数实现进程间通信。

2.5锁优化

原理

自旋锁和CAS指令都是基于原子操作指令实现,当应用程序在执行原子操作失败后,并不会释放CPU资源,而是一直循环运行直到原子操作执行成功为止,导致CPU资源浪费。如下图代码的黄色部分是一个循环等待过程:

c6507badb0f37e6d500b14d8ea96226e.png

修改方式

可以通过perf top分析占用CPU资源靠前的函数,如果锁的申请和释放在5%以上,可以考虑优化锁的实现,修改思路如下:

1. 大锁变小锁:并发任务高的场景下,如果系统中存在唯一的全局变量,那么每个CPU core都会申请这个全局变量对应的锁,导致这个锁的争抢严重。可以基于业务逻辑,为每个CPU core或者线程分配对应的资源。

2. 使用ldaxr+stlxr两条指令实现原子操作时,可以同时保证内存一致性,而ldxr+stxr指令并不能保存内存一致性,从而需要内存屏障指令(dmb ish)配合来实现内存一致性。从测试情况看,ldaxr+stlxr指令比ldxr+stxr+dmb ish指令的性能高。

3. 减少线程并发数:参考2.3.5 调整线程并发数章节。

4. 对锁变量使用Cacheline对齐:对于高频访问的锁变量,实际是对锁变量进行高频的读写操作,容易发生伪共享问题。具体优化可以参考5.2.7 Cacheline 优化章节。

5. 优化代码中原子操作的实现。下图代码为某软件的代码实现:

02fc49f6ef5143e924b848600a5b70a5.png

从函数调用逻辑上看,在while循环会重复执行原子读、变量加以及原子写入操作,代码语句多。优化思路:使用atomic_add_return指令替换这个代码流程,简化指令,提高性能。替换后的代码如下图:

0829e53668ac9f868cce78b0c5653507.png

2.6使用jemalloc优化内存分配

原理

jemalloc是一款内存分配器,与其它内存分配器(glibc)相比,其最大优势在于多线程场景下内存分配性能高以及内存碎片减少。充分发挥鲲鹏芯片多核多并发优势,推荐业务应用代码使用jemalloc进行内存分配。

在内存分配过程中,锁会造成线程等待,对性能影响巨大。jemalloc采用如下措施避免线程竞争锁的发生:使用线程变量,每个线程有自己的内存管理器,分配在这个线程内完成,就不需要和其它线程竞争锁。

修改方式

步骤 1 下载jemalloc,参考INSTALL.md编译安装。

源码下载地址https://github.com/jemalloc/jemalloc

步骤 2  修改应用软件的链接库的方式,在编译选项中添加如下编译选项:

-I`jemalloc-config--includedir`-L`jemalloc-config --libdir` -Wl,-rpath,`jemalloc-config --libdir`-ljemalloc `jemalloc-config --libs`

具体参考 https://github.com/jemalloc/jemalloc/wiki/Getting-Started

步骤 3     部分开源软件可以修改配置参数来指定内存分配库,如MySql可以配置my.cnf文件:malloc-lib=/usr/local/lib/libjemalloc.so

----结束

2.7 Cacheline 优化

原理

CPU标识Cache中的数据是否为有效数据不是以内存位宽为单位,而是以Cacheline为单位。这个机制可能会导致伪共享(false sharing)现象,从而使得CPU的Cache命中率变低。出现伪共享的常见原因是高频访问的数据未按照Cacheline大小对齐:

Cache空间大小划分成不同的Cacheline,示意图如下,readHighFreq虽然没有被改写,且在Cache中,在发生伪共享时,也是从内存中读:

6f40d0b83e03dce3e3213062e736491c.png 例如以下代码定义两个变量,会在同一个Cacheline中,Cache会同时读入: int readHighFreq, writeHighFreq 其中readHighFreq是读频率高的变量,writeHighFreq为写频率高的变量。writeHighFreq在一个CPU core里面被改写后,这个cache 中对应的Cacheline长度的数据被标识为无效,也就是readHighFreq被CPU core标识为无效数据,虽然readHighFreq并没有被修改,但是CPU在访问readHighFreq时,依然会从内存重新导入,出现伪共享导致性能降低。 鲲鹏920和x86的Cacheline大小不一致,可能会出现在X86上优化好的程序在鲲鹏 920上运行时的性能偏低的情况,需要重新修改业务代码数据内存对齐大小。X86L3 cache的Cacheline大小为64字节,鲲鹏920的Cacheline为128字节。 修改方式 1. 修改业务代码,使得读写频繁的数据以Cacheline大小对齐,修改方法可参考: a. 使用动态申请内存的对齐方法: int posix_memalign(void **memptr,size_t alignment, size_t size) 调用posix_memalign函数成功时会返回size字节的动态内存,并且这块内存的起始地址是alignment的倍数。 b. 局部变量可以采用填充的方式: int writeHighFreq; char pad[CACHE_LINE_SIZE -sizeof(int)]; 代码中CACHE_LINE_SIZE是服务器Cacheline的大小,pad变量没有用处,用于填充writeHighFreq变量余下的空间,两者之和是CacheLine大小。 2. 部分开源软件代码中有Cacheline的宏定义,修改宏的值即可。如在impala使用CACHE_LINE_SIZE宏来表示目标平台的Cacheline大小。
6f48013c25f86c00b0f695fa8feadb9b.gif 《鲲鹏性能优化十板斧系列文章(已完结) (点击可跳转查看)1.鲲鹏性能优化十板斧之前言 | 鲲鹏处理器NUMA简介与性能调优五步法 2.鲲鹏性能优化十板斧 | CPU与内存子系统性能调优 3.鲲鹏性能优化十板斧 | 网络子系统性能调优 4.鲲鹏性能优化十板斧 | 磁盘IO子系统性能调优 5.鲲鹏性能优化十板斧 | 应用程序调优(本文)
在2020年2月11日-12日期间,作为华为公司面向ICT领域全球开发者的年度顶级旗舰活动,华为开发者大会2020(Cloud)将在深圳会展中心举办,这是华为给全球开发者准备的一场规格高、受众广、内容干货丰富的顶级盛会。想了解更多?持续关注华为云,小编将为您带来关于华为开发者大会2020(cloud) 的更多精彩! 00b85cc759089d2a5505aad9ad275cc0.png 1d8244f2c17ef70829fa8dff113cfeac.png ↓点击
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值