性能优化-放开那片内存,让我来!

存储 存储软件
当你的程序中存在大量的内存分配(例如C++频繁使用string),那么可以考虑使用性能更好的内存分配库了。关于tcmalloc,jemalloc等内存分配库的对比有很多,这里有兴趣的可自行了解。

[[358893]]

本文转载自微信公众号「编程珠玑」,作者守望先生。转载本文请联系编程珠玑公众号。  

性能优化是一个常有的事情,通常来说

  • 不要过早优化-当你没有性能问题时,不需要过早考虑优化,当然对于一些代价很小,收益却很大的手段可以考虑做进来,例如最常见的就是根据业务需求选择合适的数据结构。
  • 不要过度优化。优化都是有目标的,比如你需要达到多少TPS,那么你按照这个目标去优化即可,有些优化虽然能否提升性能,但可能对代码的可维护性造成破坏。

本人对此没有过多涉猎,仅分享工作中接触到的一些内存。

内存性能问题

有很多方面会造成性能问题,例如:

  • 业务流程设计不合理,导致很多没有必要的计算
  • 数据结构选择不合适
  • 缓存使用不当

示例

假设你已经通过《perf:一个命令发现性能问题》中的方法或者使用profiler分析,已经发现内存分配是性能瓶颈:

  1. // 来源:公众号【编程珠玑】 
  2. // 作者:守望先生 
  3. // malloc.cc 
  4. #include <thread> 
  5. #include <vector> 
  6. #include <stdlib.h> 
  7. #include <string.h> 
  8. void GetMemory(){ 
  9.   for(int i = 0;i < 100000000; i++){ 
  10.     void *p = malloc(1024); 
  11.     if(NULL != p){ 
  12.       free(p); 
  13.       p = NULL
  14.     } 
  15.   } 
  16. int main(){ 
  17.   std::vector<std::thread> th; 
  18.   int nr_threads = 10; 
  19.   for (int i = 0; i < nr_threads; ++i) { 
  20.     th.push_back(std::thread(GetMemory)); 
  21.   } 
  22.   for(auto &t : th){ 
  23.     t.join(); 
  24.   } 
  25.   return 0; 

代码非常简单,仅仅是不断分配内存而已。

编译并尝试分配十亿次:

  1. $ g++ -g -o malloc malloc.cc -lpthread 
  2. time ./malloc  
  3. real    0m8.677s 
  4. user    0m29.409s 
  5. sys    0m0.029s 

分配十亿次内存,使用时间大概17s左右。另外一个终端使用perf查看情况:

  1. $ perf top -p `pidof malloc` 
  2.   52.92%  libc-2.27.so  [.] cfree@GLIBC_2.2.5 
  3.   31.94%  libc-2.27.so  [.] malloc 
  4.    8.82%  malloc        [.] GetMemory 
  5.    3.45%  malloc        [.] free@plt 
  6.    2.51%  malloc        [.] malloc@plt 
  7.    0.03%  [kernel]      [k] prepare_exit_to_usermode 
  8.    0.01%  [kernel]      [k] psi_task_change 
  9.    0.01%  [kernel]      [k] native_irq_return_iret 
  10.    0.01%  [kernel]      [k] __update_load_avg_cfs_rq 
  11.    0.01%  [kernel]      [k] __update_load_avg_se 
  12.    0.01%  [kernel]      [k] update_curr 
  13.    0.01%  [kernel]      [k] native_write_msr 
  14.    0.01%  [kernel]      [k] __schedule 
  15.    0.01%  [kernel]      [k] native_read_msr 
  16.    0.01%  [kernel]      [k] read_tsc 
  17.    0.01%  [kernel]      [k] interrupt_entry 
  18.    0.01%  [kernel]      [k] update_load_avg 
  19.    0.01%  [kernel]      [k] swapgs_restore_regs_and_return_to_usermode 
  20.    0.01%  [kernel]      [k] reweight_entity 
  21.    0.01%  [kernel]      [k] switch_fpu_return 
  22.    0.01%  [kernel]      [k] perf_event_task_tick 

从结果可以看到,大部分CPU耗费在了内存的申请和释放。

怎么办呢?第一要考虑的做法不是如何提升它,而是它能否避免?比如内存复用?而非反复申请?

比如使用内存池?但是要自己写一个稳定的内存池又需要耗费很大的精力了。怎么办呢?

性能更好的库

实际上这就引出了性能优化的一种常见方法-使用性能更好的库。那么在内存分配方面,有更好的库吗?自己又不能写出一个比libc还厉害的库,就只能用用开源的库,才能维持得了写代码的生活。

目前常见的性能比较好的内存分配库有

  • tcmalloc-谷歌开发的内存分配库
  • jemalloc

在自己编译使用redis的时候,其实你能看到它们的身影:

  1. # Backwards compatibility for selecting an allocator 
  2. ifeq ($(USE_TCMALLOC),yes) 
  3.     MALLOC=tcmalloc 
  4. endif 
  5.  
  6. ifeq ($(USE_TCMALLOC_MINIMAL),yes) 
  7.     MALLOC=tcmalloc_minimal 
  8. endif 
  9.  
  10. ifeq ($(USE_JEMALLOC),yes) 
  11.     MALLOC=jemalloc 
  12. endif 
  13.  
  14. ifeq ($(USE_JEMALLOC),no
  15.     MALLOC=libc 
  16. endif 

如何使用

这里以tcmalloc为例,看一下如何使用该库替换libc中的malloc。tcmalloc使用了thread cache,小块的内存分配都可以从cache中分配。多线程分配内存的情况下,可以减少锁竞争。

获取

你可以通过源码编译获取,github地址:https://github.com/google/tcmalloc.git

不过它需要使用bazel进行构建编译,有兴趣的可以自行尝试。

也可以直接安装:

  1. $ apt-get install -y libtcmalloc-minimal4 

安装位置查看:

  1. $ ldconfig -p | grep tcmalloc 
  2.     libtcmalloc_minimal_debug.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal_debug.so.4 
  3.     libtcmalloc_minimal.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4 
  4.     libtcmalloc_debug.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_debug.so.4 
  5.     libtcmalloc_and_profiler.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_and_profiler.so.4 
  6.     libtcmalloc.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc.so.4 

LD_PRELOAD

这种方式在自己测试的时候非常方便,只需要:

  1. $ export LD_PRELOAD=/path/to/tcmalloc.so 

导入环境变量,指定库路径即可。注意这里的/path/to更换成你的tcmalloc实际的路径。运行的时候,tcmalloc库就会被首先被使用了。

直接链接

这种方法就和普通库的使用没有什么区别了,链接使用就完事了。相关文章《静态库的制作与使用》

效果

我们使用新的库,再进行10亿次的内存分配试试:

  1. time ./malloc 
  2. real    0m7.152s 
  3. user    0m27.997s 
  4. sys    0m0.032s 

可以看到要使用的时间少了些。当然,这里的对比严格来说不是很严谨,甚至可以说起不到对比的作用。首先这里内存分配大小比较单一,并且仅有内存分配,而没有其他处理,真正是否有效果,还是要根据实际业务程序的情况来判断。当然,整体来说,tcmalloc的效果要比libc的malloc分配内存要高效。

总结

当你的程序中存在大量的内存分配(例如C++频繁使用string),那么可以考虑使用性能更好的内存分配库了。关于tcmalloc,jemalloc等内存分配库的对比有很多,这里有兴趣的可自行了解。

作者:守望,linux应用开发者,目前在公众号【编程珠玑】?分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。

 

责任编辑:武晓燕 来源: 编程珠玑
相关推荐

2022-10-30 13:21:58

谷歌Chrome浏览器

2021-11-17 08:16:03

内存控制Go

2011-05-11 17:26:17

Minify

2024-03-15 08:54:59

Linux内核NUMA

2016-08-12 10:23:28

javascriptChrome前端

2021-08-03 16:35:04

AndroidANR内存

2017-12-14 14:32:30

.Net内存代码

2016-12-22 17:21:11

Android性能优化内存泄漏

2015-09-16 15:21:23

Android性能优化内存

2019-03-14 15:38:19

ReactJavascript前端

2019-07-04 15:57:16

内存频率DDR4

2017-03-14 18:48:06

Android性能优化内存优化

2023-05-14 22:25:33

内存CPU

2023-03-27 07:39:07

内存溢出优化

2022-04-29 08:00:36

web3区块链比特币

2019-03-06 10:25:30

Web图片优化命令

2023-03-30 07:34:10

Linux性能数据结构

2022-11-11 08:16:02

java性能技术

2017-11-27 14:58:01

MySQL高并发优化性能调优

2022-04-11 09:58:07

数据库SQL
点赞
收藏

51CTO技术栈公众号