• C++程序性能控制(cpu,内存,带宽,io)


    C++程序性能控制

    常见的C++性能约束有:cpu,内存,网络带宽,磁盘读写(iops)。
    性能控制好的程序,也可以作为商用软件竞争的优势和亮点。

    在本文中将从控制与监控两个方向介绍各个性能点的处置方式。

    性能控制并不是万能的,而且多数控制方式仅能控制整个程序的最大值或者平均值,这并不是保证程序本身的性能占用就是合理的。无论哪种资源,都应该在方案设计阶段进行考虑,无论是处理过程中增加间歇,还是选择更加合理的数据结构等。

    cpu

    cpu的控制是这些性能资源中做好控制和观测的。可以直接通过cgroup进行限制,网上也有很多资料,所以本文仅简单介绍。

    通过cgroup限制cpu占用的原理

    从最简单的角度进行解释

    1. 什么是cpu占用?
      cpu占用通常来说都是指的一个百分比的值,表示着一个程序占用了系统的多少性能。将系统的性能比做有长度的传送带,将一个程序放到传送带上运行,其占用了几米,这几米占总传送带长度的百分比就是cpu占用。
    2. 多核cpu如何理解?
      现在的机器大多都是多核的了,也就有多个cpu,所以从概念上有两个cpu占用,分别是单核cpu占用和平均cpu占用。每个程序一般可以在任意一个cpu上运行,平均到每个cpu上运行的占比,就是平均cpu,同样这也是占总系统的百分比。类比到一个核上的占用就是单核cpu了,这个值是可以大于100%的。
    3. cgroup限制cpu的原理
      拿传送带模型来说,在cpu的传送带上,每一百米,会有7米在运行我们的程序,那么cpu占用就是7%。而cgroup可以控制每一米运行哪个程序,所以这就很简单,想限制到多少都是可以的,只是在这其中1%就是限制的最小力度了。对比官方的概念,每一米就是一个时间片,在cgroup控制时,可以控制分子和分母,即[运行时间片个数]/[每经历多少个时间片的时间]。

    综上所述,通过cgroup限制后,cpu就能基本保证是可控的了。

    代码控制cpu占用

    1. 适当增加睡眠
      • 在计算密集度高的位置,可能导致较高的cpu占用,可以适当增加sleep,控制长期占用cpu,挤占同程序其他模块的正常cpu活动。但这也是存在缺陷的,在编译阶段可能打断编译优化,在运行阶段(特别是在有gpu的机器上)会导致大量计算缓存被浪费。
    2. 为单个线程绑定特定的cpu运行
      • 一个系统上可能有多个cpu,但这些cpu也可以是不同型号的,有的cpu计算能力更好,有的cpu读取缓存更快等。在C++程序上可以通过pthread_setaffinity_np将一个线程绑定到一个确定的cpu上运行。
      • cpu的结构中是存在一些缓存的,如果指定一个线程运行还是有可能继续使用到这部分缓存,加快运行速度的。
    3. 调整合适的cgroup限制值
      • 设置cgroup限制cpu时,需要提供两个值,表示百分比的分子和分母。如想设置为7%的cpu占用(单核)。这时可以设置「7,100」,也可以设置「70,1000」,「700,10000」等。分母越大,允许程序连续运行的时间越长,有利于使用到缓存加速。但相对的瞬时cpu更可能出现超过平均值很多的情况。

    cpu监控

    • cmd命令:top pidstat等都可以对cpu占用进行监控。另外,其他比较强力的工具还有perf top -p 28764,可以直接看到cpu占用发生在哪个阶段,可以看作很简略的火焰图
    • proc目录下的stat文件中存在对应列,在通过计算后可以得到cpu。具体计算方式较为复杂,可以参考:https://tool.4xseo.com/a/50844.html
    • 火焰图进行性能分析

    内存

    程序内存的控制不能向cpu那样,直接控制后就不会超了。通过cgroup限制,当程序内存超出限值后,杀死进程,这仅能保证此程序的运行不会影响到其他程序的运行。

    cgroup限制内存异常场景:由于交换区被占满,导致系统卡死,虽然程序内存超限,但cgroup也需要很久才能检测到进程超内存。

    代码控制内存占用

    代码中控制内存占用的方式,其实也都很容易想到,程序中会占用内存的有几部分。

    • 代码中申请使用的栈/堆等内存
    • 加载的三方so所占用的内存
    1. 优化代码中申请的内存

      • 使用合理的数据结构,并合理控制队列长度
      • 尽量不使用new独立申请内存
      • 减少太占用内存的结构,如过长的vector,Json,避免Json的值传递,超大的json等。
      • 减少内存碎片的产生。适当使用内存池等,减少频繁的申请较大的内存。
    2. 减少三方so的内存占用

      • 对于手动通过dlopen打开的so,可以考虑使用RTLD_LAZY类型加载so,减少簿部分内存占用。参考https://zhuanlan.zhihu.com/p/560349203
      • 选择so时,可以考虑使用系统中自带的三方库,如果已有程序加载此so,也会与其共享这部分so的符号,减少部分内存占用

    内存监控

    内存监控有粗力度的,只监控进程上总体使用了多少内存。如使用pidstat命令-r参数查看,查看status文件中的值。也有可以用于内存泄漏问题定位的关注每个函数申请了多少内存,有没有被释放。后者需要编译带有符号表的程序,通过valgrind等工具进行检测,后面会补充一下如何细节定位内存泄漏问题,以及内存使用情况问题。

    带宽

    后补充

    磁盘访问(iops

    磁盘访问指的是读写磁盘,对于当下的磁盘来说,这读写次数/频率已经都不是问题了,但是对于老版的磁盘,可能还有这样的限制。

    iops限制

    1. 可以通过cgroup对io读写进行限制,但这里有cgroup v1和v2版本的区别。对于v1版本,监控iops时,仅是能监控进程直接写入磁盘的,而通常我们使用write函数写入文件时,先会写到用户态的缓冲区,即使调用flush也只是刷新到内核的缓冲区,后续在内核线程的调度过程中,真正写入到磁盘中。这时候需要使用directio直接写入磁盘,才能被v1版本监控到io使用。而v2版本,在4.15版本的内核被引入,但默认使用的都还是v1版本。在v2版本中,会跟踪每一条访问,准确限制进程的io访问。

    2. 增加内存盘的方式,高频使用的文件存储到内存盘中,减少对磁盘的读写

    iops的监控

    可以使用iotop监控,或者perf命令监控一些挂载点的调用。某些情况下,也可以将整机的io使用,看作是单个进程的io占用

  • 相关阅读:
    前端进阶--深入理解JavaScript
    Spire.PDF for .NET【文档操作】演示:动态创建 PDF 并将其发送到客户端浏览器
    问题1.用PGP解密出keybox.xml,过程中报“Can‘t check signature: No public key”如图,这个正常吗?如何解决?
    shell 循环语句
    java从入门到起飞(八)——循环和递归
    程序人生 | 编程的上帝视角应该怎么去找
    vivado产生报告阅读分析12-时序报告8
    vue PasswordStrength密码强度组件
    Atcoder ABC340 C - Divide and Divide
    从零开始入门单片机(一):必会背景知识总结
  • 原文地址:https://blog.csdn.net/xiaonuo911teamo/article/details/133521457