字符串的优化

糟糕的字符串连接函数

C++中字符串是按照值的形式实现的,又因为C++中字符串底层是使用动态内存实现的,因此、在项目中对字符串的优化必不可少,也是性能优化的重点。

假如代码中有如下remove_ctrl函数的实现:

std::string remove_ctrl(std::string s) {std::string result;for (int i=0; i<s.length(); ++i) {if(s[i] >= 0x20)result = result + s[i];}return result;
}

remove_ctrl() 在循环中对通过参数接收到的字符串 s 的每个字符进行处理。循环中的代码就是导致这个函数成为热点的原因。 if 条件语句从字符串中得到一个字符,然后与一个字面常量进行比较。这里没有什么问题。但是第 5 行的语句就不一样了。

正如之前所指出的,字符串连接运算符的开销是很大的。它会调用内存管理器去构建一个新的临时字符串对象来保存连接后的字符串。如果传递给 remove_ctrl() 的参数是一个由大于0x2(可打印)的字符组成的字符串,那么 remove_ctrl() 几乎会为 s 中的每个字符都构建一个临时字符串对象。对于一个由 100 个字符组成的字符串而言,这会调用 100 次内存管理器来为临时字符串分配内存,调用 100 次内存管理器来释放内存

对上述代码进行基准测试,结果如下:

-------------------------------------------------------------------------
Benchmark                               Time             CPU   Iterations
-------------------------------------------------------------------------
bench_remove_ctrl_operator          12433 ns        12432 ns        54195

如果你的代码编辑器带自动检查,就会发现这样的代码连代码编辑器都受不了,给你显示一大堆波浪号。

使用复合赋值操作避免临时字符串

注意观察上面实现的第5行代码result = result + s[i];,因为字符串是按照值处理的,等号右边的值想加后会在挨个赋值到等号左边,这样如果参数字符串有n个字符,那么remove_ctrl会复制O(n[^2])个自负,所有这些内存分配和复制都会导致性能变差。

为了减少字符串的复制,可以使用复合操作符+=,使用复合操作符之后的代码如下:

std::string remove_ctrl(std::string s) {std::string result;for (int i=0; i<s.length(); ++i) {if(s[i] >= 0x20)result += s[i];}return result;
}

改进之后测试结果:

-------------------------------------------------------------------------
Benchmark                               Time             CPU   Iterations
-------------------------------------------------------------------------
bench_remove_ctrl_operator          12433 ns        12432 ns        54195
bench_remove_ctrl_operator_opt      12400 ns        12400 ns        56281

可以看出测试结果中改进之后的代码明显比没有改进之前快很多,而且这个时间会随着传入的字符串的长度增长而增加,传入的字符串越长,没有改进的代码需要复制的就越多。

通过预留存储空间来减少内存的重新分配

经过上述优化之后,result仍然存在在执行的过程中,由于缓存区不够需要重新分配的情况,导致频繁分配内存,可以通过提前分配内存优化该问题,结果如下:

std::string remove_ctrl_reserve(std::string s) {std::string result;result.reserve(s.length());for (int i=0; i< (int)s.length(); ++i) {if(s[i] >= 0x20)result += s[i];}return result;
}

运行结果:

-------------------------------------------------------------------------
Benchmark                               Time             CPU   Iterations
-------------------------------------------------------------------------
bench_remove_ctrl_operator          12170 ns        12168 ns        56990
bench_remove_ctrl_operator_opt      12390 ns        12388 ns        57518
bench_remove_ctrl_reserve             767 ns          767 ns       908214

消除对参数字符串的复制

上述方法中还是会对参数字符串进行一次复制,由于内存分配是非常昂贵的,因此哪怕是一次内存分配也值得从程序中去除,优化之后参数按照引用传递,结果如下:

std::string remove_ctrl_ref_args(std::string &s) {std::string result;result.reserve(s.length());for (int i=0; i< (int)s.length(); ++i) {if(s[i] >= 0x20)result += s[i];}return result;
}

更改之后运行结果:

-------------------------------------------------------------------------
Benchmark                               Time             CPU   Iterations
-------------------------------------------------------------------------
bench_remove_ctrl_operator          12158 ns        12159 ns        56439
bench_remove_ctrl_operator_opt      12044 ns        12046 ns        57409
bench_remove_ctrl_reserve             762 ns          762 ns       921342
bench_remove_ctrl_ref_args            737 ns          738 ns       938882

移除对返回值的复制

经过上述的优化之后,性能已经有了很大的改善。但是仔细观察代码还是能发现存在不必要的内存申请的地方,那就是返回值,每次在处理好字符串之后,返回时还需要复制一次,这时也会申请内存。我们完全可以将字符串通过引用传出去,改进之后的代码如下:

void remove_ctrl_ref_result_it(std::string &result, std::string const &s) {result.clear();result.reserve(s.length());for (auto &it:s) {if (it >= 0x20)result += it;}
}

改进之后的运行结果如下:

--------------------------------------------------------------------------
Benchmark                                Time             CPU   Iterations
--------------------------------------------------------------------------
bench_remove_ctrl_operator           11948 ns        11949 ns        55770
bench_remove_ctrl_operator_opt       12088 ns        12089 ns        58264
bench_remove_ctrl_reserve              767 ns          767 ns       899659
bench_remove_ctrl_ref_args             742 ns          742 ns       948853
bench_remove_ctrl_ref_args_it          758 ns          758 ns       922450
bench_remove_ctrl_ref_result_it        493 ns          493 ns      1382138

可以看到,当返回值也通过引用返回时,函数调用的耗时一下子降低到了493纳秒。

用字符串代替数组

当程序有严格的性能要求的时候,不是使用C++风格的字符串,而应该使用C风格的字符数组,使用字符数组需要程序员自己控制内存的申请和释放,能够最大程度提升性能,但是带来的后果就是维护的成本增加

void remove_ctrl_cstrings(char* destp, char const* srcp, size_t size) {for (size_t i=0; i<size; ++i) {if (srcp[i] >= 0x20)*destp++ = srcp[i];}*destp = 0;
}

使用C风格的字符数组测试结果:

--------------------------------------------------------------------------
Benchmark                                Time             CPU   Iterations
--------------------------------------------------------------------------
bench_remove_ctrl_operator           12103 ns        12103 ns        55630
bench_remove_ctrl_operator_opt       12175 ns        12175 ns        57324
bench_remove_ctrl_reserve              765 ns          765 ns       918301
bench_remove_ctrl_ref_args             746 ns          746 ns       938322
bench_remove_ctrl_ref_args_it          751 ns          751 ns       912842
bench_remove_ctrl_ref_result_it        497 ns          497 ns      1394954
bench_remove_ctrl_cstrings             270 ns          270 ns      2575663

使用更好的算法

有没有即好维护又有高性能的的方法,答案是有的,那就是使用更好的算法。

经过对以上的改进的思考,我们发现只要想办法能够减少内存的申请和释放,字符串也能有很高的性能,这就是新算法需要下手的地方。

因为、老的实现方式,是每个值都要对比之后,挨个添加到result中,这就导致频繁的操作内存,新算法中使用append一段一段的取符合条件的字符串添加到result中,实际测试结果141纳秒左右,已经优于C风格的字符串,这就要感谢算法带来的buff加成。

void remove_ctrl_block_append(std::string &result, const std::string &s) {result.clear();result.reserve(s.length());for (size_t b=0,i=b; b < s.length(); b = i+1) {for (i=b; i<s.length(); ++i) {if (s[i] < 0x20) break;}result.append(s, b, i-b);}
}

运行结果:

--------------------------------------------------------------------------
Benchmark                                Time             CPU   Iterations
--------------------------------------------------------------------------
bench_remove_ctrl_operator           12258 ns        12257 ns        54639
bench_remove_ctrl_operator_opt       12347 ns        12347 ns        56488
bench_remove_ctrl_reserve              765 ns          765 ns       896059
bench_remove_ctrl_ref_args             736 ns          736 ns       947049
bench_remove_ctrl_ref_args_it          746 ns          746 ns       926601
bench_remove_ctrl_ref_result_it        494 ns          494 ns      1394869
bench_remove_ctrl_cstrings             270 ns          270 ns      2562077
bench_remove_block_append              141 ns          141 ns      4941262

当然,还可以使用剔除不符合要求字符的方式实现:

void remove_ctrl_erase(std::string &s) {for (size_t i = 0; i < s.length();)if (s[i] < 0x20)s.erase(i,1);else ++i;
}

测试结果:

--------------------------------------------------------------------------
Benchmark                                Time             CPU   Iterations
--------------------------------------------------------------------------
bench_remove_ctrl_operator           12146 ns        12146 ns        56901
bench_remove_ctrl_operator_opt       12254 ns        12254 ns        56758
bench_remove_ctrl_reserve              756 ns          756 ns       909901
bench_remove_ctrl_ref_args             735 ns          735 ns       938884
bench_remove_ctrl_ref_args_it          748 ns          748 ns       922788
bench_remove_ctrl_ref_result_it        494 ns          494 ns      1422658
bench_remove_ctrl_cstrings             275 ns          275 ns      2534856
bench_remove_block_append              143 ns          143 ns      4871896
bench_remove_erase                     169 ns          169 ns      4104307

当然、还可以使用更优秀的库,使用优化性能更好的编译器,或者为自己的业务专门定制一个字符串的类,来达到最优。

C++性能优化-字符串的优化相关推荐

  1. C#优化字符串操作【月儿原创】

    C#优化字符串操作 作者:清清月儿 主页:http://blog.csdn.net/21aspnet/           时间:2007.6.17 开发中可以说几乎随时会涉及到字符串处理,本人觉得很 ...

  2. mysql 按日期拆分成多条记录_mysql性能优化2 设计规范 设计原则 结构优化 拆分 配置优化...

    一.MYSQL数据库设计规范 1.数据库命名规范 a.采用26个英文字母(区分大小写)和0-9的自然数(经常不需要)加上下划线'_'组成; b.命名简洁明确(长度不能超过30个字符); c.例如:us ...

  3. MongoDB 教程五: MongoDB固定集合和性能优化 (索引Indexes, 优化器, 慢查询profile)

    mongodb索引详解(Indexes) 索引介绍 索引在mongodb中被支持,如果没有索引,mongodb必须扫描每一个文档集合选择匹配的查询记录.这样扫描集合效率并不高,因为它需要mongod进 ...

  4. Android 系统性能优化(55)---Android 性能优化之内存优化

    Android 性能优化之内存优化 前言 Android App优化这个问题,我相信是Android开发者一个永恒的话题.本篇文章也不例外,也是来讲解一下Android内存优化.那么本篇文章有什么不同 ...

  5. web页面性能优化及SEO优化

    web页面性能优化 前言: 在同样的网络环境下,两个同样能满足你的需求的网站,一个"Duang"的一下就加载出来了,一个纠结了半天才出来,你会选择哪个?研究表明:用户最满意的打开网 ...

  6. 前端性能优化(图片优化)

    从输入URL到页面加载完成的过程:首先通过DNS(域名解析)把URL解析为对应的IP地址,然后与该IP地址确定的服务器建立起TCP网络连接.随后向服务器发送HTTP请求,服务器处理完HTTP请求后把目 ...

  7. 性能优化之MySQL优化(转)

    MYSQL优化浅谈 原文地址:https://www.cnblogs.com/moss_tan_jun/p/8025517.html 参考慕课网学习视频:性能优化之MySQL优化 msyql是开发常用 ...

  8. android—性能优化2—内存优化

    文章目录 性能优化: 工具: memory profiler LeakCanary arthook epic 库 java内存管理机制 java 内存回收机制 Android内存管理机制 Dalvik ...

  9. 性能优化之MySQL优化

    文章目录 第1章 数据库优化简介 数据库优化的目的 数据库优化的方式 第2章 SQL语句优化 SELECT语句 - 语法顺序: SELECT语句 - 执行顺序: 2-1 数据准备 2-2 MySQL慢 ...

最新文章

  1. 零起点学算法01——第一个程序Hello World!
  2. C 语言和 C++、C# 的区别在什么地方?
  3. 如何用纯 CSS 创作一个精彩的彩虹 loading 特效
  4. 液态金属和Liquidmetal公司
  5. Android Studio xml文件中的布局预览视图
  6. 「Python」pandas入门教程
  7. mysql约束教程,MySQL 约束
  8. C++各个算数类型占用的字节数
  9. 汇编中求补指令NEG的小问题..
  10. HTML5/CSS3笔记
  11. 一个java错误的总结
  12. javaweb中真分页案例
  13. 移动硬盘在服务器上读不出来怎么办,移动硬盘无法识别的usb设备怎么办
  14. jQuery weui 时间选择器datetimepicker只用年月日
  15. CS领域论文数据分析
  16. 主板电源开关接口图解_【转】图解:各种主板接线方法 主板电线接法(电源开关、重启等)...
  17. 什么是Express框架
  18. 的计算机基本操作知识,电脑的基本操作知识有哪些
  19. 国际物流杰信项目总结与面试
  20. Chrome 浏览器远程调试

热门文章

  1. SQLServer2008 视图创建实例
  2. nyoj1121周期串
  3. Struts2基础学习总结
  4. NYOJ 801 Haffman编码
  5. mysql 修改字符集
  6. mysql与web服务器之间的分离操作
  7. 核心概念 —— 门面(Facades)
  8. PHP 汉字转拼音(首拼音,所有拼音)
  9. C# Image 、 byte[] 、Bitmap之间的转化
  10. [导入]WCF后传系列(6):消息如何传递之绑定Part 1