C/C++字符串使用军规

C/C++字符串使用军规 

1. 概述

本文对常见的C++ string使用方式进行了测试,并结合源代码分析,总结出如何高效的使用C++ string对象。

2. 测试情况

2.1. 测试环境

测试环境信息如下:


配置项目


配置信息


备注


CPU


8 * 2


Intel(R) Xeon(R) CPU E5620  主频2.40GHz, 物理CPU 2个,逻辑CPU 16个


内存


24G


6块 * 4G  DDR3 1333 REG


OS


Redhat 5


Linux platform2 2.6.18-164.el5 #1 SMP Tue Aug 18 15:51:48 EDT 2009 x86_64 x86_64 x86_64 GNU/Linux


编译器


gcc 4.1.2


gcc version 4.1.2 20080704 (Red Hat 4.1.2-48)

 

2.2. 测试结果

测试结果如下:


操作(1M次)


性能(ms)


C语言函数


C语言性能(ms)


备注


创建空串


13


NA


NA


NA


创建一个“test”串


85


char[]=”test”


5


NA


“=”操作


95


strcpy()


16


 


“+=”操作


95


strcat()


25


两个字符串长度都是10


“+”操作


125


strcat()


循环“+”10长度字符串


2852631


strcat()


769268


C++的“+”操作和C的strcat操作性能都很差,但原因不一样


循环“+=”10长度字符串


43


strlen() +

sprintf()


3877099


C代码如下:

sprintf(pos, "%s", part);

len = strlen(buffer);

pos = buffer + len;


函数参数传引用


40


NA


NA


NA


函数参数传值


100


NA


NA


NA


返回string局部变量


110


NA


NA


NA


size()操作


4


strlen()


40


字符串长度为10


“==”操作


43


strcmp()


22


两个长度为10的字符串比较

 

2.3. 数据分析

1)构造“test”串的时间是构造空串的时间的6倍

2)“=”和“+=”时间相近

3)“+”操作比“+=”操作性能要低30%

4)循环“+”操作的性能极低,而循环“+=”好很多

5)传引用和传值的效率相差2.5倍,传值和返回对象的时间基本相同;

6)size()操作是恒定时间,strlen()是和字符串长度线性相关的;

3. 源码分析

3.1. string的内存管理

string的内存申请函数实现如下(为了阅读方便,去掉了注释和一些辅助代码,详见gcc源码/libstdc++-v3/include/bits/basic_string.tcc):


template<typename _CharT, typename _Traits, typename _Alloc>

    typename basic_string<_CharT, _Traits, _Alloc>::_Rep*

    basic_string<_CharT, _Traits, _Alloc>::_Rep::

    _S_create(size_type __capacity, size_type __old_capacity,

          const _Alloc& __alloc)

    {

      // _GLIBCXX_RESOLVE_LIB_DEFECTS

      // 83.  String::npos vs. string::max_size()

      if (__capacity > _S_max_size)

    __throw_length_error(__N("basic_string::_S_create"));

 

      const size_type __pagesize = 4096;

      const size_type __malloc_header_size = 4 * sizeof(void*);

 

      //如下代码进行空间大小计算,采用了指数增长的方式,即:如果要求的空间__capacity小于当前空间__old_capacity的2倍,则按照当前空间的2倍来申请。

      if (__capacity > __old_capacity && __capacity < 2 * __old_capacity)

    __capacity = 2 * __old_capacity;

 

      // NB: Need an array of char_type[__capacity], plus a terminating

      // null char_type() element, plus enough for the _Rep data structure.

      // Whew. Seemingly so needy, yet so elemental.

      size_type __size = (__capacity + 1) * sizeof(_CharT) + sizeof(_Rep);

 

      const size_type __adj_size = __size + __malloc_header_size;

      if (__adj_size > __pagesize && __capacity > __old_capacity)

    {

      const size_type __extra = __pagesize - __adj_size % __pagesize;

      __capacity += __extra / sizeof(_CharT);

      // Never allocate a string bigger than _S_max_size.

      if (__capacity > _S_max_size)

        __capacity = _S_max_size;

      __size = (__capacity + 1) * sizeof(_CharT) + sizeof(_Rep);

    }

 

      //此处开始分配空间,第一步使用allocate函数申请空间,第二步使用new (__place)的方式生成一个对象返回。此处分两步的主要原因应该是内存分配和释放是由allocator实现的,string对象只使用内存,所以使用定位new的方式返回对象给string,这样string本身无法delete内存。

      void* __place = _Raw_bytes_alloc(__alloc).allocate(__size);

      _Rep *__p = new (__place) _Rep;

      __p->_M_capacity = __capacity;

      __p->_M_set_sharable();

      return __p;

    }

 

gcc中的allocator实现如下(详见gcc源码/libstdc++-v3/include/ext/new_allocator.h):


      pointer

      allocate(size_type __n, const void* = 0)

      {

    if (__builtin_expect(__n > this->max_size(), false))

      std::__throw_bad_alloc();

      //如下代码使用new函数申请内存

    return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));

      }

 

3.2. 常见操作

 

3.2.1. “=”

代码如下(详见gcc源码/libstdc++-v3/include/bits/ basic_string.h):


      basic_string&

      operator=(const basic_string& __str)

      { return this->assign(__str); }

 

其中assign实现如下(详见gcc源码/libstdc++-v3/include/bits/ basic_string.tcc):


  template<typename _CharT, typename _Traits, typename _Alloc>

    basic_string<_CharT, _Traits, _Alloc>&

    basic_string<_CharT, _Traits, _Alloc>::

    assign(const basic_string& __str)

    {

      if (_M_rep() != __str._M_rep())

    {

      // XXX MT

      const allocator_type __a = this->get_allocator();

      _CharT* __tmp = __str._M_rep()->_M_grab(__a, __str.get_allocator());

      _M_rep()->_M_dispose(__a);

      _M_data(__tmp);

    }

      return *this;

    }

 

_M_grab函数实现如下(详见gcc源码/libstdc++-v3/include/bits/ basic_string.h):


    _CharT*

    _M_grab(const _Alloc& __alloc1, const _Alloc& __alloc2)

    {

      return (!_M_is_leaked() && __alloc1 == __alloc2)

              ? _M_refcopy() : _M_clone(__alloc1);

       }

 

通过_M_grab函数可以看出,对于同一个_Alloc对象即同一块内存,使用引用记数,否则使用clone进行拷贝。

clone的操作最后调用如下代码(详见gcc源码/libstdc++-v3/include/bits/ char_traits.h):


      static char_type*

      copy(char_type* __s1, const char_type* __s2, size_t __n)

      { return static_cast<char_type*>(memcpy(__s1, __s2, __n)); }

 

 

3.2.2. “+”

代码如下(详见gcc源码/libstdc++-v3/include/bits/ basic_string.h)


  template<typename _CharT, typename _Traits, typename _Alloc>

    basic_string<_CharT, _Traits, _Alloc>

    operator+(const basic_string<_CharT, _Traits, _Alloc>& __lhs,

          const basic_string<_CharT, _Traits, _Alloc>& __rhs)

    {

      //第一步:生成一个string对象__str包含左值

      basic_string<_CharT, _Traits, _Alloc> __str(__lhs);

      //第二步:将右值append到__str

      __str.append(__rhs);

      //第三步:返回局部变量__str

      return __str;

    }

 

通过以上代码可以看出,“+”操作耗费的性能是很大的:第一步创建一个对象,在函数结束时析构对象,第三步调用拷贝构造函数构造临时对象,然后在赋值结束后析构对象。

 

对于一个连加的表达式,这样的耗费更加可观,例如如下语句:

string str1 = str2 +str3 + str4 +str5;

则以上过程会执行3次,总共6次构造和析构操作,而且随着+次数越来越多,字符串越来越长,构造析构成本更高。测试数据显示连续操作100万次,耗费时间达到了惊人的2852631ms!

3.2.3. “+=”

代码如下(详见gcc源码/libstdc++-v3/include/bits/ basic_string.h)


      basic_string&

      operator+=(const basic_string& __str)

      { return this->append(__str); }

 

通过以上代码可以看出,“+=”操作的代码很简单,只是简单的append,不需要额外的局部变量和临时变量,因此性能也会高得多。这也是测试数据两者相差巨大的原因。

 

append函数最终调用如下函数完成操作(详见gcc源码/libstdc++-v3/include/bits/ char_traits.h):


      static char_type*

      copy(char_type* __s1, const char_type* __s2, size_t __n)

      { return static_cast<char_type*>(memcpy(__s1, __s2, __n)); }

 

但我们还要继续深入思考以下:为什么“+”操作要这样做呢?我个人认为原因应该是“+”操作支持连加的原因,例如str1 = str2 +str3 + str4 +str5。

 

3.2.4.  “==”操作

“==“操作最终的实现代码如下:


      static int

      compare(const char_type* __s1, const char_type* __s2, size_t __n)

      { return memcmp(__s1, __s2, __n); }

 

通过代码可以看出,string“==”操作最终使用的是memcmp函数实现。

 

3.2.5. size()

size()函数实现如下(详见gcc源码/libstdc++-v3/include/bits/ basic_string.h):


      size_type

      size() const

      { return _M_rep()->_M_length; }

 

通过代码可以看出,对于string对象来说,已经使用了一个成员变量来记录字符串长度,而不需要像C语言的strlen()函数那样采用遍历的方式来求长度,这也是C++的“+=”操作性能比C的strlen+sprintf或者strcat操作高出几个数量级的原因。

4. 使用指南

从以下几方面来看,大型项目推荐使用C++的字符串:

1) 测试结果来看,除了+操作外,100万次操作的性能基本都在100ms以内;

2) 从源码分析来看,string的操作最终基本上都是调用mem*函数,这和C语言的字符串也是一致的;

3) string对象封装了内存管理,操作方便,使用安全简单,不会像C语言字符串那样容易导致内存问题(溢出、泄露、非法内存);

4) 使用“+=” 循环拼接字符串性能优势很明显;

 

但在使用过程中为了尽可能的提高性能,需要遵循以下原则:

l  函数调用时使用传引用,而不要使用传值,不需要改变的参数加上const修饰符

l  使用“+=”操作,而不要使用“+”操作,即使写多个“+=”也无所谓

例如将str1 = str2 +str3 + str4 +str5写成如下语句:

str1 += str2;

str1 += str3;

str1 += str4;

str1 += str5;

 

同样,C语言的字符串处理性能总体上要比C++ string性能高,但同样需要避免C语言的性能缺陷,即:

l  要尽量避免显示或者隐式(strcat)求字符串的长度,特别是对长字符串求长度。

例如,测试用例中C语言的循环字符串拼接操作sprintf + strlen并不是唯一的实现方式,参考C++ string的实现,C语言拼接字符串优化的方式如下(测试结果是78ms):


        len = strlen(part);             //计算需要拼接的字符串长度

        memcpy(pos, part, len);   //使用memcpy将字符串拼接到目标字符串末尾

            pos += len;                      //重置目标字符串的结尾指针

 

时间: 2024-09-16 17:56:10

C/C++字符串使用军规的相关文章

yahoo网站性能优化的建议:Yahoo军规再度挖掘

文章描述:本来这是个老生常谈的问题,上周自成又分享了一些性能优化的建议,我这里再做一个全面的Tips整理,谨作为查阅型的文档,不妥之处,还请指正. 本来这是个老生常谈的问题,上周自成又分享了一些性能优化的建议,我这里再做一个全面的Tips整理,谨作为查阅型的文档,不妥之处,还请指正:如果你已经对yahoo这些优化建议烂熟于心,果断点这里 一. Yahoo的军规条例: 谨记:80%-90%的终端响应时间是花费在下载页面中的图片,样式表,脚本,flash等:详细的解释来这里查:http://deve

58到家数据库30条军规解读

军规适用场景:并发量大.数据量大的互联网业务 军规:介绍内容 解读:讲解原因,解读比军规更重要 一.基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务.行级锁.并发性能更好.CPU及内存缓存页优化使得资源利用率更高 (2)必须使用UTF8字符集 解读:万国码,无需转码,无乱码风险,节省空间 (3)数据表.数据字段必须加入中文注释 解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的 (4)禁止使用存储过程.视图.触发器.Event 解读:高并发大数据的互联网业务,架构设计思路是"解放

【MySql】赶集网mysql开发36条军规

写在前面的话: 总是在灾难发生后,才想起容灾的重要性: 总是在吃过亏后,才记得曾经有人提醒过. (一)核心军规 (1)不在数据库做运算    cpu计算务必移至业务层: (2)控制单表数据量    int型不超过1000w,含char则不超过500w:    合理分表:    限制单库表数量在300以内: (3)控制列数量    字段少而精,字段数建议在20以内: (4)平衡范式与冗余    效率优先:    往往牺牲范式: (5)拒绝3B    拒绝大sql语句:big sql    拒绝大事

Python检测字符串中是否包含某字符集合中的字符

  这篇文章主要介绍了Python检测字符串中是否包含某字符集合中的字符,需要的朋友可以参考下 目的 检测字符串中是否包含某字符集合中的字符 方法 最简洁的方法如下,清晰,通用,快速,适用于任何序列和容器 代码如下: def containAny(seq,aset): for c in seq: if c in aset: return True return False 第二种适用itertools模块来可以提高一点性能,本质上与前者是同种方法(不过此方法违背了Python的核心观点:简洁,清

在Python中处理字符串之isdecimal()方法的使用

  这篇文章主要介绍了在Python中处理字符串之isdecimal()方法的使用,是Python入门学习的基础知识,需要的朋友可以参考下 isdecimal()方法检查字符串是否仅由十进制字符组成.此方法只存在于unicode对象. 注意:要定义一个字符串为Unicode,只需前缀分配'u'左引号.以下是示例. 语法 以下是isdecimal()方法的语法: ? 1 str.isdecimal() 参数 NA 返回值 如果字符串中的所有字符为十进制此方法返回true,否则返回false. 例子

php查询相似度最高的字符串的方法

 这篇文章主要介绍了php查询相似度最高的字符串的方法,涉及php操作字符串及数组实现相似度算法的技巧,具有一定参考借鉴价值,需要的朋友可以参考下     本文实例讲述了php查询相似度最高的字符串的方法.分享给大家供大家参考.具体如下: 根据传入的字符串和数组,返回数组中相似度最高的字符串 1. PHP代码如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function closest_word($input, $words) { $shortest

md5 16位二进制与32位字符串相互转换

 密码很多时候都会用 md5保存,并且很多时候都是16位二进制格式的md5,php 里面 md5($str, true) 可以很方便的获取.更多时候md5结果是一组32个字符组成的字符串,其实转换很简单   代码如下: <?php   $str = 'test'; $cm = md5($str); $bm = md5($str, true);   $cstr = implode(unpack('H*', $bm)); $bstr = pack('H*', $cm);     echo 'str:

PHP字符串的连接

 这篇文章主要介绍了PHP字符串的连接的简单实例,有需要的朋友可以参考一下 很多时候我们需要将几个字符串连接起来显示,在PHP中,字符串之间使用"点"来连接,也就是英文中的句号".",具体使用方式如下: 代码如下: <?php   //定义字符串   $str1 = "Hello World!";   $str2 = "Welcome to HutaoW's BLOG!";     //连接上面两个字符串 中间用空格分隔

ASP中一个字符串处理类(VBScript)

vbscript|字符串 这个类是用于处理字符串的,是老外写的,我把里面的功能和参数加了说明 使用方法: =============== test.asp================ <!--#include file="StringOperations.asp"--> <%dim strset str = New StringOperations test = str.toCharArray("check this out") respons