【问题标题】:std string concatenation performancestd 字符串连接性能
【发布时间】:2014-04-23 03:41:49
【问题描述】:

在性能方面,现代C++ 编译器中的以下函数有什么区别吗?

std::string ConcatA(const std::string& a, const std::string& b, const std::string& c)
{
    return a + b + c;
}

std::string ConcatB(const std::string& a, const std::string& b, const std::string& c)
{
    std::string r = a;
    r += b;
    r += c;
    return r;
}

【问题讨论】:

  • 分别循环 10,000 次并比较差异如何?
  • 虽然ConcatA 构造了多个字符串,但它可能比ConcatB 更快,因为在第二个中可能存在额外的重新分配和复制。
  • 当产生这样的连接时,源范围的“连接视图”可以提供不错的性能优势(甚至可以避免创建连接的字符串)。这种连接的一个例子是boost::range::joinboost.org/doc/libs/1_55_0/libs/range/doc/html/range/reference/…;它只需要 2 个源范围,但可以使用 c++11 进行真正的多范围连接)。
  • 当需要大量连接时,另一个选择是使用连接友好的字符串类,例如古老的 __gnu_cxx::crope

标签: c++ performance string-concatenation stdstring


【解决方案1】:

ConcatB 有 1 个临时字符串,而 ConcatA 有 2 个临时字符串,因此 ConcatB 快两倍。

$ cat cata.cpp

#include <string>
#include <iostream>
std::string ConcatA(const std::string& a, const std::string& b, const std::string& c)
{
    return a + b + c;
}
int main(){
  std::string aa="aa";
  std::string bb="bb";
  std::string cc="cc";
  int count = 0;
  for(int ii = 0; ii < 10000000; ++ii) {
    count += ConcatA(aa, bb, cc).size();
  }
    std::cout<< count <<std::endl;
}

$ cat catb.cpp

#include <string>
#include <iostream>
std::string ConcatB(const std::string& a, const std::string& b, const std::string& c)
{
    std::string r = a;
    r += b;
    r += c;
    return r;
}
int main(){
  std::string aa="aa";
  std::string bb="bb";
  std::string cc="cc";
  int count = 0;
  for(int ii = 0; ii < 10000000; ++ii) {
    count += ConcatB(aa, bb, cc).size();
  }
    std::cout<< count <<std::endl;
}

$ clang++ -v

Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.1.0
Thread model: posix

$ clang++ cata.cpp
$ time ./a.out

60000000

real    0m1.122s
user    0m1.118s
sys 0m0.003s

$ clang++ catb.cpp
$ time ./a.out
60000000

real    0m0.599s
user    0m0.596s
sys 0m0.002s
$

【讨论】:

    【解决方案2】:

    我用 MinGW (TDM) 4.8.1 编译它,带有选项 -fdump-tree-optimized,没有 -O2

    第一个动作像

    string tmp = a+b; // that mean create new string g, g += b, tmp = g (+dispose g)
    tmp += c;
    return tmp; // and dispose tmp
    

    第二个用另一种方式做

    string tmp = a; // just copy a to tmp
    tmp += b;
    tmp += c;
    return tmp; // and dispose tmp
    

    看起来像这样

      void * D.20477;
      struct basic_string D.20179;
    
      <bb 2>:
      D.20179 = std::operator+<char, std::char_traits<char>, std::allocator<char> > (a_1(D), b_2(D)); [return slot optimization]
      *_3(D) = std::operator+<char, std::char_traits<char>, std::allocator<char> > (&D.20179, c_4(D)); [return slot optimization]
    
      <bb 3>:
    
      <bb 4>:
      std::basic_string<char>::~basic_string (&D.20179);
      D.20179 ={v} {CLOBBER};
    
    <L1>:
      return _3(D);
    
    <L2>:
      std::basic_string<char>::~basic_string (&D.20179);
      _5 = __builtin_eh_pointer (1);
      __builtin_unwind_resume (_5);
    

      void * D.20482;
      struct string r [value-expr: *<retval>];
    
      <bb 2>:
      std::basic_string<char>::basic_string (r_1(D), a_2(D));
      std::basic_string<char>::operator+= (r_1(D), b_3(D));
    
      <bb 3>:
      std::basic_string<char>::operator+= (r_1(D), c_4(D));
    
      <bb 4>:
    
    <L0>:
      return r_1(D);
    
    <L1>:
      std::basic_string<char>::~basic_string (r_1(D));
      _5 = __builtin_eh_pointer (1);
      __builtin_unwind_resume (_5);
    

    因此,在应用 -O2 优化后,编译器将 ConcatB 函数保持在几乎相同的视图中,并通过内联函数、向内存分配部分添加常量值、声明新函数来使 ConcatA 发挥一些魔力,但最有价值的部分保持不变。

    连接A:

      D.20292 = std::operator+<char, std::char_traits<char>, std::allocator<char> > (a_2(D), b_3(D)); [return slot optimization]
      *_5(D) = std::operator+<char, std::char_traits<char>, std::allocator<char> > (&D.20292, c_6(D));
    

    连接B:

      std::basic_string<char>::basic_string (r_3(D), a_4(D));
      std::basic_string<char>::append (r_3(D), b_6(D));
      std::basic_string<char>::append (r_3(D), c_8(D));
    

    所以,很明显 ConcatB 比 ConcatA 好,因为它执行的分配操作更少,当您尝试优化这么小的代码时,这非常昂贵。

    【讨论】:

      猜你喜欢
      • 2010-09-12
      • 2010-10-03
      • 1970-01-01
      • 2022-09-30
      • 1970-01-01
      • 1970-01-01
      • 2015-10-14
      • 2017-12-24
      • 2013-09-10
      相关资源
      最近更新 更多