如何编写【C++高性能服务器/程序】

1 篇文章 0 订阅
1 篇文章 0 订阅

总结了一些关于性能方面的知识/经验,跟大家分析下。
欢迎大家在评论进行补充(我会按照补充添加)。
希望大家点赞收藏

1. 避免频繁内存申请(避免频繁系统函数调用)

  • 系统调用通常涉及到与操作系统内核的交互
  • 操作通常涉及资源调度、复杂的数据结构和算法、异常校验
  • 频繁内存分配会造成内存碎片,导致操作系统可能需要耗时寻找足够大的连续内存块
  • 系统调用本身是线程安全的,操作系统内核会在内部使用各种技术和算法来确保系统调用的正确性和线程安全性。
    建议: 在需要频繁调用场景下,多了解需使用的系统函数调用内部实现原理,选择一些更优的方案替换系统调用,或一些策略减少系统调用.

2. 避免不必要的内存访问

(1) 尽量Cache命中

  • 遵循局部性原理,即尽量让数据在物理内存上连续存储,以减少缓存未命中的次数。这可以通过适当的数组和结构体布局来实现。
  • 尽量减少跨页或跨块的数据访问,因为这可能导致更高的内存访问延迟
  • 常用局部变量加上register关键字
    建议编译器尽可能地把变量存放在寄存器中,以加快其访问速度。

(2) 使用数据缓存

  • 如果某些数据被频繁访问,考虑将它们缓存在更快的存储介质中,如CPU缓存或寄存器

  • 在某些情况下,使用内存池或对象池可以避免频繁的内存分配和释放,从而提高性能
    概念1: 存储金字塔
    在这里插入图片描述
    ** 越靠近 CPU 速度越快,容量越小,价格越贵
    ** 每一种存储器设备只和它相邻的存储设备打交道 比如,CPU Cache 是从内存里加载而来的,或者需要写回内存,并不会直接写回数据到硬盘,也不会直接从硬盘加载数据到 CPU Cache 中,而是先加载到内存,再从内存加载到 Cache 中。

    概念2: 高速缓存-Cache
    ** CPU和内存之间速度严重不匹配的问题,在CPU和内存之间设计了高速缓存,即Cache。
    在这里插入图片描述
        CPU读取数据时,会在L1、L2、L3Cache中逐级查找,如果找到,就从Cache直接读取,找不到再从内存读取,并且把数据存放到Cache中,以便提高下次访问的效率。
        在这个过程中,如果在Cache中找到所需数据,称为Cache命中(Cache Hit), 找不到称为Cache未命中(Cache Miss)。
    L1 Cache命中的时候,读取数据最快,性能最好,而当L1、L2、L3 Cache全部未命中时,就必须要直接从内存中读取数据,性能最差

    概念3: 局部性原理
    ** 时间局部性。如果一个数据在某个时间点被CPU访问了,那么在接下来很短的一段时间内,这个数据很有可能会再次被CPU访问到。
    ** 空间局部性。如果一个数据在某个时间点被CPU访问了,那么与这个数据临近的其他数据,很有可能也会很快被CPU访问到。

    概念4: 缓存行-Cache Line
    ** 根据 局部性原理 如果一个数据被CPU访问了,那么这个数据相邻的其他数据也很快会被访问到。因此,为了提高内存数据的读取效率,并且最大化利用CPU资源,数据在Cache和内存之间传输时,不是一个字节一个字节进行传输的,而是以缓存行(Cache Line)为单位进行传输的。

(3) 数据结构优化
    设计/选择 合适的数据结构和算法,避免不必要的数据复制和遍历。选择合适的数据结构可以减少内存访问次数和数据移动操作。

(4) 尽量确保内存对齐
    大多数现代处理器在访问未对齐的内存时,性能会下降。这是因为处理器通常一次从内存中读取或写入多个字节(例如,4字节、8字节等),如果数据未对齐,处理器可能需要多次访问内存来获取或存储数据,这被称为“未对齐访问”或“跨边界访问”。因此,内存对齐可以显著提高数据访问的性能。

(5) 考虑使用内联函数
    将那些短小的、频繁调用的函数声明为内联函数

3. 避免不必要的内存拷贝

(1) 使用引用或指针传递参数 或 做为返回值

  • 减少 临时变量创建 构造 析构

(2) 使用零拷贝技术

  • 一些函数调用
    ** std::move使用移动语义和完美转发, C++11引入了右值引用和移动语义,允许对象在不需要时“移动”其资源,而不是拷贝
    ** std::swap: 交换两个对象的内容。对于支持移动语义的对象,swap可以在不进行数据复制的情况下交换资源
    ** 容器的emplace方法 (如:emplace_back, emplace, try_emplace): 在容器的指定位置直接构造元素,通常比push_back或insert更高效,因为它们避免了临时对象的创建和复制。
    ** std::unique_ptr 和 std::shared_ptr: 智能指针通过控制对象的所有权,可以避免对象的复制,并且可以在多个指针之间转移所有权。
    ** std::string_view 或 std::span (C++20): 这些是非拥有的引用类型,它们提供了对数据的视图,而不是复制。它们可以用来访问字符串或数组的一部分,而不需要进行复制。
    ** 发送文件sendfile: 它可以在两个文件描述符之间直接传输数据,常用于将数据从文件直接发送到网络套接字。这避免了将数据从内核拷贝到用户空间,然后再从用户空间拷贝回内核的情况。 原理:sendfile直接在内核中操作,将文件内容直接从文件系统缓存拷贝到套接字缓冲区,省去了用户空间的介入。
    ** 使用“placement new”语法: 允许在已分配的内存上直接构造对象,避免了额外的内存分配和复制。
  • 内存映射 (Memory-Mapped Files):
    ** 内存映射文件通过将磁盘上的文件映射到进程的地址空间来实现零拷贝,直接在内存上操作文件数据,无需通过read/write系统调用。这样可以避免用户空间和内核空间之间的数据拷贝。 原理:使用mmap系统调用将文件映射到进程的虚拟内存空间。对这块内存的读写操作将直接影响到磁盘上的文件内容。
  • 共享内存 (Shared Memory)
    ** 允许多个进程访问同一块内存区域,这样数据就可以在不同的进程之间共享而无需复制。
  • 零拷贝数据传输
    ** 在数据包到达网络适配器后,网络协议栈可以直接将数据包传输到应用程序的内存中,而不是先将数据包复制到内核缓冲区,再从内核缓冲区复制到用户空间。
  • 一些策略
    ** 循环缓冲区 (Circular Buffers): 在生产者-消费者模型中,循环缓冲区可以使生产者和消费者共享缓冲区而无需进行数据拷贝。
    ** 写时复制 (Copy-On-Write, COW): 当多个进程需要读取同一份数据时,它们可以共享同一份物理内存。只有当其中一个进程需要修改数据时,才会创建这份数据的副本。
    ** 使用大页内存 (Large Pages): 大页内存可以减少页表项的数量,从而减少TLB(Translation Lookaside Buffer)缓存不命中的情况,提高内存访问效率。
    ** 避免使用缓冲区: 直接I/O (Direct I/O) 允许数据从磁盘直接传输到应用程序的数据结构中,而不是先复制到操作系统的缓冲区。选择合理的数据结构.

(3) 使用合理的数据结构
    合理设计数据结构可以减少内存拷贝。例如,使用连续存储的数组而不是链表,可以减少访问和拷贝的开销。

(4) 避免容器扩容
    选择好需使用容器后(如std::vector), 尽量通过resize、reserve来提前分配好内存, 来避免或 减少 扩容次数。 (经实测小数据对象下(如int)reserve后一个个去赋值 快于resize后一个个 push_back, 可能是因为push_back多了很多单次修改std::vector成员属性, 校验等…)

(5) 避免不必要的临时对象
    在函数调用或表达式求值过程中,尽量减少临时对象的创建。这可以通过使用const引用参数、返回值优化等技术来实现

4. 避免不必要的计算

(1) 合理进行程序设计 找出最优解决方案
(2) 使用更高效的算法和数据结构
(3) 使用缓存机制
    使用变量存储中间结果,避免在循环中重复计算相同的表达式。使用记忆化技术或缓存机制,特别是在递归算法中.

5. 使用第三方工具协助找到性能评价,并进行调优

(1) Valgrind
    主要用于内存泄漏检测,但它的工具集中的Callgrind可以用于性能分析。
(2) gprof
    GNU Profiler 是一个标准的UNIX/Linux命令行性能分析工具,输出程序的调用图和执行时间统计。
(3) perf
    Linux性能计数器工具,可以分析CPU性能计数器和程序事件,如缓存未命中、分支预测错误等。
(4) Intel VTune Profiler
    一款功能强大的性能分析工具,适用于Intel处理器,提供了详细的硬件级别性能数据。
(5) Visual Studio Profiler
    Visual Studio集成了性能分析工具,可以在Windows平台上进行性能分析和调试。
(6) Very Sleepy
    一个Windows平台上的C/C++ CPU性能分析工具,可以分析函数调用次数和时间消耗。
(7) AQtime
    一款商业性能分析工具,支持多种编程语言和开发环境,提供详细的性能报告。
(8) AMD uProf
AMD uProf是AMD的性能分析工具,提供了对CPU性能事件的分析。
(9) Google’s CPU Profiler
    部分Google性能工具套件(gperftools),可以用来记录和分析程序的CPU使用情况

6. 使用编译器优化:

(1) GCC和Clang
-O0:不进行优化,编译速度最快,适用于调试。
-O1:开启基本优化,编译器会尝试减少代码大小和执行时间,而不会显著增加编译时间。
-O2:开启进一步优化,比-O1更激进,包括所有不涉及空间-时间权衡的优化。
-O3:启用-O2中的所有优化,并添加更多优化,如更积极的循环优化和内联函数。
-Os:优化代码大小,可能会牺牲一些执行速度。
-Ofast:启用所有-O3优化,并启用一些可能不遵守严格标准兼容性的优化策略。
-flto:启用链接时优化,允许在链接时进行更多的优化。
(2) Microsoft Visual C++(MSVC):
/Od:禁用优化,适合调试。
/O1, /O2:对应于GCC和Clang中的-O1和-O2。
/Ox:使用最大优化(相当于-O2或-O3)。
/Ot:优先优化速度。
/Os:优先优化大小。
/Oy:省略帧指针。
/GL:启用全程序优化。
(4) Intel C++ Compiler
类似于GCC和Clang的优化选项,但还提供一些特定于Intel处理器的优化选项。

7. NUMA(Non-Uniform Memory Access)架构

    用于多处理器系统,其中每个处理器都有自己的本地内存。处理器可以访问本地内存比访问远程内存(属于其他处理器的本地内存)更快
(1) 改进的内存访问性能:
通过将线程和内存分配给特定的NUMA节点,可以减少对远程内存的访问,从而减少访问延迟,提高内存访问速度。
(2) 高度并行性:
NUMA架构允许多个处理器同时访问多个内存节点,增加了系统的并行处理能力,这对于并行计算和多线程应用程序来说是非常重要的。
(3) 提高大型多核系统的效率:
对于拥有大量核心的系统,NUMA架构有助于减少每个核心在内存访问时的争用,因为每个核心能够更频繁地访问本地内存。

8. 多线程合理使用锁

(1) 最小化锁的使用:
    只在绝对必要的时候使用锁。尽量减少锁的粒度,只保护临界区,即实际需要同步的最小代码块。
(2) 避免锁竞争:
    尽量设计无锁的数据结构或算法。
    使用局部变量代替共享变量,减少需要锁定的资源。
    将数据分解为更小的块,以减少不同线程之间的竞争。
(3) 使用自旋锁:
    对于持锁时间非常短的场景,使用自旋锁可能比互斥锁(mutex)效率更高,因为自旋锁避免了线程上下文切换的开销。
(4) 读写锁(共享-独占锁):
    当读操作远多于写操作时,使用读写锁可以提高性能。读写锁允许多个读线程同时访问资源,但写线程会独占锁。
(5) 分离互斥:
    如果可能,将数据结构分离,使不同的线程操作不同的锁,从而减少锁的争用。
(6) 锁分级:
    使用分级锁来减少死锁的可能性,并提高锁的性能。
(7) 条件变量:
    结合互斥锁使用条件变量,只在必要时挂起和唤醒线程,减少不必要的锁争用。
(8) 锁粗化:
    如果频繁地锁定同一资源,考虑将多个操作合并为一个大的锁定区域(锁粗化),以减少锁的开销。
(9) 避免长时间持锁:
    尽量不要在持有锁的时候进行I/O操作、计算密集型任务或者可能导致线程挂起的操作。

9. 减少线程切换

(1) 使用亲和性绑定:
    (线程核心绑定) 将线程绑定到特定的处理器核心上,这样可以减少线程在不同核心间迁移,从而减少缓存失效和上下文切换。
(2) 减少线程数量:
    尽量使用不多于处理器核心数的线程。过多的线程会导致频繁的上下文切换,增加开销。
(3) 使用线程池:
    线程池可以复用一组线程来执行多个任务,减少线程创建和销毁的开销。
(4) 避免过度同步:
    多线程合理使用锁,尽量避免不必要的同步,以减少因等待锁而导致的线程挂起和切换。
(5) 合理分配任务:
    尽量将任务合理分配给线程,使线程可以持续工作一段时间,避免频繁的任务切换。
(6) 使用工作窃取策略:
    对于任务并行库,可以减少线程间的同步,从而减少切换。
(7) 避免长时间的阻塞操作:
    尽量减少线程执行I/O操作或其他可能导致线程阻塞的操作。可以使用异步I/O或将I/O操作分离到特定的I/O线程。
(8) 合理设置线程优先级:
    谨慎设置线程优先级,以防止优先级倒置或饿死问题。
(9) 减少虚假唤醒:
    使用条件变量时,减少不必要的唤醒操作。
(10) 使用并发数据结构:
    使用无锁或者低锁的并发数据结构,如C++11中的std::atomic和std::concurrent_queue。
(11) 避免昂贵的操作:
    在锁定资源时,避免在临界区内进行昂贵的操作,如内存分配或复杂的计算。

10. 减少流水线停顿 增加指令级并行率

冲突包含
(1)数据冲突(Data Hazard) 数据间依赖
(2)控制冲突(Control Hazard) 遇到分支跳转 无法预测
(3)结构冲突(Structure Hazard) 多条指令同时竞争同一个硬件资源

需要你做的(编译器也会帮你做)

  • 避免数据冲突
  • 循环展开(Loop Unrolling): 减少循环中的控制逻辑,可以降低分支预测错误的影响,从而减少流水线停顿。
    如图避免依赖(数据冲突) 和循环展开如图避免依赖(数据冲突) 和循环展开
  • 软件流水线(Software Pipelining): 手动重新组织代码,使得每个CPU周期都有多个操作在不同的执行阶段,以减少资源冲突和等待时间。
  • 指令调度(Instruction Scheduling): 编译器通常会尝试重新排列指令的顺序来避免执行单元的闲置和减少数据相关性导致的停顿。手动调整代码语句的顺序也可以达到相似的效果。
  • 分支预测优化(Branch Prediction Optimization): 尽量减少代码中的分支,尤其是在循环和频繁执行的路径中。如果不可避免,则通过排序操作或使用分支预测提示来帮助编译器优化。
    在这里插入图片描述
  • 多线程(Multithreading): 在软件层面使用多线程可以让多个处理器核心并行工作

不需要你做的, 但需要你了解贴合 (由系统、CPU架构、硬件支持)

  • 超标量架构: CPU架构 可以在一个时钟周期内发射 和执行多条指令,从而提高并行度。
  • 指令向量化: 或称为单指令多数据(SIMD),是一种并行计算架构,它允许一条指令同时对多个数据元素执行相同的操作(如SSE、AVX2、AES…)。
  • 乱序执行: CPU架构 允许指令在不违反数据依赖性的情况下乱序执行,以利用所有可用的执行单元。
  • 指令重排: CPU架构 编译器或处理器可以重排指令顺序,以减少流水线停顿
  • 分支预测: CPU架构 通过高效的分支预测算法,处理器可以猜测分支指令的结果,以减少由于控制冒险引起的停顿。
  • 分支目标缓冲区: CPU架构 使用BTB存储分支目标地址,这样当分支被预测为“跳转”时可以快速获取跳转地址。
  • 投机执行: CPU架构 在分支预测的基础上进行投机执行,以填充那些因分支预测而闲置的流水线阶段。
  • 数据前推: CPU架构 在数据生产和消费之间提供一条直接路径,使得后续指令可以直接使用前一条指令的结果,而不必等待其写回寄存器。
  • 延迟加载: CPU架构 编译器或处理器推迟加载操作,将计算指令插入到加载和使用该数据的指令之间,减少数据冒险。
  • 多线程支持: CPU架构 支持多线程,当一个线程遇到长时间的停顿时,处理器可以快速切换到另一个线程,减少流水线空闲时间。
  • 硬件预取: 使用硬件预取逻辑来提前加载可能很快会被访问的数据,减少因缓存未命中引起的停顿。
  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux高性能服务器项目是一个基于Linux操作系统的服务器项目,旨在提供高性能、高可靠性、高安全性的服务器解决方案。该项目采用了先进的技术和优化策略,可以满足各种规模和需求的企业和个人用户的需求。该项目的特点包括高效的资源利用、可扩展性强、易于管理和维护等。同时,该项目还提供了丰富的应用程序和工具,可以满足用户的各种需求。 ### 回答2: Linux高性能服务器项目是针对构建高性能服务器应用程序开发的一种工具集,主要用于解决服务器应用程序对高并发、高扩展性和高可用性的需求。 在传统的服务器应用程序开发中,常常会使用C/C++语言开发,但是由于这些语言的代码效率低下、开发效率较低等问题,在高并发、高扩展性和高可用性的需求下表现不尽如人意。因此,Linux高性能服务器项目通过提供针对Linux操作系统的高性能应用程序开发框架和工具集,使开发人员可以更高效地构建高性能服务器应用程序Linux高性能服务器项目包括众多开源工具和库,如Nginx、Redis、Mysql、Postgresql、Apache、Tomcat、Memcached等。这些工具和库可以在Linux下构建高性能服务器应用程序,并支持高并发、高扩展性和高可用性。 其中,Nginx是最为著名的Linux高性能服务器应用程序开发工具之一,其采用事件驱动、非阻塞I/O等高效构架,能够提供高并发、高吞吐量和低内存消耗等特点。Nginx同时支持多种反向代理、负载均衡和缓存等功能,在大规模网站和高并发访问场景下表现尤为突出。 另外,在Linux高性能服务器项目中,Mysql和Postgresql是两个重要的关系型数据库软件,它们可以提供高效的数据访问和管理功能,而Redis和Memcached则是两个重要的缓存软件,能够提供高速缓存和分布式缓存等功能,从而进一步提高服务器性能。 总之,Linux高性能服务器项目是一个完整的工具集,支持开发人员构建高性能、高可用和高扩展性的服务器应用程序,有助于提高网站和应用的性能和用户体验。 ### 回答3: Linux高性能服务器项目是指基于Linux系统开发、优化和部署的高性能服务器系统。随着互联网和移动互联网的发展,海量数据的处理和高并发的请求成为了许多企业必须面对的问题。而Linux作为一种自由开放的操作系统,在服务器领域有着广泛的应用,并且具备高度的可定制性和实时性,因此成为了众多企业构建高性能服务器系统的首选。 Linux高性能服务器项目的核心是系统优化,包括系统内核优化、网络配置优化、磁盘IO优化、内存管理优化等,通过这些手段提升系统的性能和稳定性,进一步提高服务器的吞吐量和并发处理能力。在系统优化的基础上,还需要根据不同的应用场景进行架构设计和开发,构建高可靠、高可扩展、低延迟的服务器系统。 在实际项目中,Linux高性能服务器系统往往需要承载大量的用户请求和数据处理任务,因此需要具备高度的稳定性和可靠性。在系统架构上需要采用分布式集群架构,通过负载均衡、故障切换等手段提高系统的可用性和可靠性。同时,还需要采用自动化运维工具,对系统进行监控、调优和故障处理,提高系统运维效率和问题响应速度。 总之,Linux高性能服务器项目是一项综合性工程,需要多方面的知识和技能。但是相信随着技术的不断发展和应用的不断拓展,Linux高性能服务器系统将会在越来越多的领域得到应用,并且发挥更加重要的作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值