看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第五篇~

当字符串数据的所有权已经确定(譬如由某个string对象持有),并且你只想访问(而不修改)他们时,使用 std::string_view 可以避免字符串数据的复制,从而提高程序效率,这(指程序效率)也是这篇文章的主要内容.

这次要介绍的 string_view 是 C++17 的一个主要特性.

我假设你已经了解了一些 std::string_view 的知识,如果没有,可以看看我之前的这篇文章.C++ 中的 string 类型在堆上存放自己的字符串数据,所以当你处理 string 类型的时候,很容易就会产生(堆)内存分配.

Small string optimisation

我们先看下以下的示例代码:

#include <iostream>
#include <string>void* operator new(std::size_t count)
{std::cout << "   " << count << " bytes" << std::endl;return malloc(count);
}void getString(const std::string& str) {}int main()
{std::cout << std::endl;std::cout << "std::string" << std::endl;std::string small = "0123456789";std::string substr = small.substr(5);std::cout << "   " << substr << std::endl;std::cout << std::endl;std::cout << "getString" << std::endl;getString(small);getString("0123456789");const char message[] = "0123456789";getString(message);std::cout << std::endl;return 0;
}

代码第4到第8行,我重载了全局的 new 操作符,这样我就能跟踪(堆)内存的分配了,而后,代码分别在第18行,第19行,第27行,第29行创建了string对象,所以这几处代码都会产生(堆)内存分配.相关的程序输出如下:

咦, 程序竟然没有产生内存分配?这是怎么回事?其实 string 类型只有在字符串超过指定大小(具体实现相关)时才会申请(堆)内存,对于 MSVC 来说,指定大小为 15, 对于 GCC 和 Clang,这个值则为 23.

这也就意味着,较短的字符串数据是直接存储于 string 的对象内存中的,不需要分配(堆)内存.

从现在开始,示例代码中的字符串将拥有至少30个字符,这样我们就不需要关注短字符串优化了.好了,带着这个前提(字符串长度>=30个字符),让我们重新开始讲解.

No memory allocation required

现在, std::string_view 无需复制字符串数据的优点就更加明显了(std::string不进行短字符串优化的情况下),下面的代码就是例证.

#include <cassert>
#include <iostream>
#include <string>
#include <string_view>void* operator new(std::size_t count)
{std::cout << "   " << count << " bytes" << std::endl;return malloc(count);
}void getString(const std::string& str) {}void getStringView(std::string_view strView) {}int main()
{std::cout << std::endl;std::cout << "std::string" << std::endl;std::string large = "0123456789-123456789-123456789-123456789";std::string substr = large.substr(10);std::cout << std::endl;std::cout << "std::string_view" << std::endl;std::string_view largeStringView{ large.c_str(), large.size() };largeStringView.remove_prefix(10);assert(substr == largeStringView);std::cout << std::endl;std::cout << "getString" << std::endl;getString(large);getString("0123456789-123456789-123456789-123456789");const char message[] = "0123456789-123456789-123456789-123456789";getString(message);std::cout << std::endl;std::cout << "getStringView" << std::endl;getStringView(large);getStringView("0123456789-123456789-123456789-123456789");getStringView(message);std::cout << std::endl;return 0;
}

代码22行,23行,39行,41行因为创建了 string 对象 所以会分配(堆)内存,但是代码29行,30行,47行,48行,49行也相应的创建了 string_view 对象,但是并没有发生(堆)内存分配!

这个结果令人印象深刻,(堆)内存分配是一个非常耗时的操作,尽量的避免(堆)内存分配会给程序带来很大的性能提升,使用 string_view 能提升程序效率的原因也正是在此,当你需要创建很多 string 的子字符串时, string_view 带来的效率提升将更加明显.

O(n) versus O(1)

std::string 和 std::string_view 都有 substr 方法, std::string 的 substr 方法返回的是字符串的子串,而 std::string_view 的 substr 返回的则是字符串子串的"视图".听上去似乎两个方法功能上比较相似,但他们之间有一个非常大的差别: std::string::substr 是线性复杂度, std::string_view::substr 则是常数复杂度.这意味着 std::string::substr 方法的性能取决于字符串的长度,而std::string_view::substr 的性能并不受字符串长度的影响.

让我们来做一个简单的性能对比:

#include <chrono>
#include <fstream>
#include <iostream>
#include <random>
#include <sstream>
#include <string>
#include <vector>#include <string_view>static const int count = 30;
static const int access = 10000000;int main()
{std::cout << std::endl;std::ifstream inFile("grimm.txt");std::stringstream strStream;strStream << inFile.rdbuf();std::string grimmsTales = strStream.str();size_t size = grimmsTales.size();std::cout << "Grimms' Fairy Tales size: " << size << std::endl;std::cout << std::endl;// random valuesstd::random_device seed;std::mt19937 engine(seed());std::uniform_int_distribution<> uniformDist(0, size - count - 2);std::vector<int> randValues;for (auto i = 0; i < access; ++i) randValues.push_back(uniformDist(engine));auto start = std::chrono::steady_clock::now();for (auto i = 0; i < access; ++i) {grimmsTales.substr(randValues[i], count);}std::chrono::duration<double> durString = std::chrono::steady_clock::now() - start;std::cout << "std::string::substr:      " << durString.count() << " seconds" << std::endl;std::string_view grimmsTalesView{ grimmsTales.c_str(), size };start = std::chrono::steady_clock::now();for (auto i = 0; i < access; ++i) {grimmsTalesView.substr(randValues[i], count);}std::chrono::duration<double> durStringView = std::chrono::steady_clock::now() - start;std::cout << "std::string_view::substr: " << durStringView.count() << " seconds" << std::endl;std::cout << std::endl;std::cout << "durString.count()/durStringView.count(): " << durString.count() / durStringView.count() << std::endl;std::cout << std::endl;return 0;
}

展示程序结果之前,让我先来简单描述一下:测试代码的主要思路就是读取一个大文件的内容并保存为一个 string ,然后分别使用 std::string 和 std::string_view 的 substr 方法创建很多子字符串.我很好奇这些子字符串的创建过程需要花费多少时间.

我使用了<格林童话>作为程序的读取文件.代码中的 grimmTales(第22行) 存储了文件的内容.代码34行中我向 std::vector 填充了 10000000 个范围为[0, size - count - 2]的随机数字.接着就开始了正式的性能测试.代码37行到40行我使用 std::string::substr 创建了很多长度为30的子字符串,之所以设置长度为30,是为了规避 std::string 的短字符串优化.代码46行到49行使用 std::string_view::substr 做了相同的工作(创建子字符串).

程序的输出如下,结果中包含了文件的长度, std::string::substr 所花费的时间, std::string_view::substr 所花费的时间以及他们之间的比例.我使用的编译器是 GCC 6.3.0.

Size 30

没有开启编译器优化的结果:

开启编译器优化的结果:

编译器的优化对于 std::string::substr 的性能提升并没有多大作用,但是对于 std::string_view::substr 的性能提升则效果明显.而 std::string_view::substr 的效率几乎是 std::string::substr 的 45 倍!

Different sizes

那么如果我们改变子字符串的长度,上面的测试代码又会有怎样的表现呢?当然,相关测试我都开启了编译器优化,并且相关的数字我都做了3位小数的四舍五入.

对于上面的结果我并不感到惊讶,这些数字正好反应了 std::string::substr 和 std::string_view::substr 的算法复杂度. std::string::substr 是线性复杂度(依赖于字符串长度), std::string_view::substr 则是常数复杂度(不依赖于字符串长度).最后的结论就是: std::string_view::substr 的性能要大幅优于 std::string::substr.

[译]C++17,使用 string_view 来避免复制相关推荐

  1. [译]C++17,标准库有哪些新变化?

    看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第二篇~ C++17 有许多新的标准库变化,简单起见,这篇文章只介绍了以下内容:std::s ...

  2. [译]C++17,标准库新引入的并行算法

    看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第七篇~ C++17 对 STL 算法的改动,概念上其实很简单.标准库之前有超过100个算法 ...

  3. LeetCode 第 17 场双周赛(469/897,前52.3%)

    文章目录 1. 比赛结果 2. 题目 LeetCode 5143. 解压缩编码列表 easy LeetCode 5144. 矩阵区域和 medium LeetCode 5145. 祖父节点值为偶数的节 ...

  4. c#截取字符串后几位_基础库的字符串设计

    C++在字符串上表现,一直以来很受人诟病,没有一个库的字符串类的表现能让人满意, std的string,mfc的CString,Qt的QString等等字符串类,都存在这样那样的问题,以至于字符串处理 ...

  5. 不可多得的MBA相关书籍推荐

    这份MBA书籍及教材清单已经被众多MBA专业的学子们验证,这些书大多理论及实践兼顾,是不可多得的MBA相关书籍.其中,除榜首的<MBA必读12篇>只能从12Reads官网获得外,其他书籍均 ...

  6. EasyMesh_Specification_v4中英文

    1 Overview 2 References [1] IEEE Computer Society, "IEEE Standard for Information Technology – ...

  7. 【C++实战 】标准库

    文章目录 文本处理 认识字符串 sring使用 1. 字面量后缀 2. 原始字符串 3. 字符串转换函数 4. 字符串视图类 正则表达式 容器 容器的通用特效 有序容器 无序容器 算法 认识算法 迭代 ...

  8. 你们知道第一个发Nature的中国人是谁吗?

    现在我们搞科研,个个都梦想能发CNS(Cell,Nature,Science),很多人搞一辈子科研可能都发不了一篇CNS上的文章. 随着中国国力和科学技术的进步,现在一些顶级学校的实验室发CNS也不是 ...

  9. 商务印书馆汉译世界学术名著丛书目录

    摘自:http://www.qiji.cn/scinews/detailed/5718.html 一.哲学类(桔黄色) 二.历史·地理类(黄色) 三.政治·法律·社会类(绿色) 四.经济(蓝色) 五. ...

  10. 银雀山汉简出土引发全球“孙子出版热”

    安乐哲翻译了<孙子兵法:首部含有新发现的银雀山汉墓竹简的英译本的新译本>,后又翻译<孙膑兵法>. 中新网北京1月29日电(韩胜宝) 中央电视台大型文博探索节目<国家宝藏& ...

最新文章

  1. 合并报表编制采用的理论_合并报表操作的整体思路梳理
  2. 编码练习——Java-4-字符串
  3. 你还傻傻的分不清“和服和浴衣吗?
  4. weblogic 用的人还多吗_2020年劳务工越来越多,出现这种“怪现状”,是发展的需求吗?...
  5. WebStrom里设置angular提示,可以在html中提示ts文件的内容
  6. java数组的用法_Java数组的使用
  7. ES6之let原理+回调函数等待队列——五个完全相同的按钮,点第i个按钮弹出i
  8. python 贴吧调度器_简单的Python调度器Schedule详解
  9. 【Java进阶】Java并发包提供了哪些并发工具类?
  10. igbt原理动画演示视频_IGBT的结构与工作原理 测量方法详细讲解
  11. 儒豹公布09年7月手机搜索热门关键词排行榜
  12. 力扣——算法入门计划第十四天
  13. 一文学会如何做电商数据分析(附运营分析指标框架)
  14. 浏览器的判断和Window系统是64位还是32位的判断
  15. 音频信号耦合为何要用极性电容?如何选型?
  16. Android后台开启服务默默拍照
  17. 情侣一起看同步看电影H5网站 电影同步观看平台 (自己写的 已开源)
  18. 离散数学期末考试必考知识点
  19. 温故而知新CentOS宝塔
  20. 利用 Matlab/Simulink 平台搭建双馈风力发电机在电网中的模型,双馈风力发电机在风速变化的影响下转矩、电流、电压等参数波形变化

热门文章

  1. matlab神经网络工具箱使用教程
  2. 全志平台Android开关核进程迁移导致游戏卡顿调试记录
  3. Windows XP和Windows 7双系统安装说明和注意事项
  4. OpenCASCADE7.6编译
  5. AngularJS 模块
  6. 投票计数python
  7. 陕西计算机在职研究生院校排名,陕西在职研究生哪个学校好上
  8. Tech Talk| Redmi K50 电竞版手机极致散热技术详解
  9. python百度爬虫_Python爬虫 - 简单抓取百度指数
  10. 测试人员日常基本工作流程