字符串中最优化的串联方式


72

我们总是每天遇到很多情况,其中我们必须在代码中执行繁琐的字符串操作。我们都知道,字符串操作是昂贵的操作。我想知道哪个是可用版本中最便宜的。

最常见的操作是串联(这可以在某种程度上控制)。在C ++中串联std :: strings的最佳方法是什么,以及各种加速串联的解决方法?

我的意思是,

std::string l_czTempStr;

1).l_czTempStr = "Test data1" + "Test data2" + "Test data3";

2). l_czTempStr =  "Test data1"; 
    l_czTempStr += "Test data2";
    l_czTempStr += "Test data3";

3). using << operator

4). using append()

另外,相对于std :: string,使用CString有什么好处吗?


6
你为什么不能测量?无论如何,stringstream不是为此用例构建的string。因此,开始时可能是一个不错的选择stringstream
Magnus Hoff 2013年

3
1. ITYM不合法l_czTempStr = std::string("Test data1") + "Test data2" + "Test data3";。除此之外,答案是计时不同的技术。变量太多,不可能回答这个问题。答案取决于您正在使用的字符串的数量和长度,加上要编译的平台以及要编译的平台。
约翰

7
它实际上是瓶颈吗?然后进行基准测试。通常,最快的方法是在附加任何数据之前为所有数据预先分配足够的空间,并避免使用临时变量(+创建新对象,C ++ 11中有一些特殊情况)。但是除非需要,否则不要对其进行优化,否则您的代码将变得不可读。
戴夫

6
@MagnusHoff你已经倒退了。 std::ostringstream是为格式化而设计的,通常仅在需要格式化时使用。他的所有数据都是字符串,因此,std::string并置是首选解决方案。
James Kanze 2013年

3
附带说明:对于很长的字符串,使用Rope代替字符串可能是个好主意。
ComicSansMS 2013年

Answers:


75

这是一个小型测试套件:

#include <iostream>
#include <string>
#include <chrono>
#include <sstream>

int main ()
{
    typedef std::chrono::high_resolution_clock clock;
    typedef std::chrono::duration<float, std::milli> mil;
    std::string l_czTempStr;
    std::string s1="Test data1";
    auto t0 = clock::now();
    #if VER==1
    for (int i = 0; i < 100000; ++i)
    {
        l_czTempStr = s1 + "Test data2" + "Test data3";
    }
    #elif VER==2
    for (int i = 0; i < 100000; ++i)
    {
        l_czTempStr =  "Test data1"; 
        l_czTempStr += "Test data2";
        l_czTempStr += "Test data3";
    }
    #elif VER==3
    for (int i = 0; i < 100000; ++i)
    {
        l_czTempStr =  "Test data1"; 
        l_czTempStr.append("Test data2");
        l_czTempStr.append("Test data3");
    }
    #elif VER==4
    for (int i = 0; i < 100000; ++i)
    {
        std::ostringstream oss;
        oss << "Test data1";
        oss << "Test data2";
        oss << "Test data3";
        l_czTempStr = oss.str();
    }
    #endif
    auto t1 = clock::now();
    std::cout << l_czTempStr << '\n';
    std::cout << mil(t1-t0).count() << "ms\n";
}

coliru上

编译如下:

clang ++ -std = c ++ 11 -O3 -DVER = 1 -Wall -pedantic -pthread main.cpp

21.6463毫秒

-DVER = 2

6.61773毫秒

-DVER = 3

6.7855毫秒

-DVER = 4

102.015毫秒

看起来2)+=是赢家。

(无论是否编译-pthread似乎都会影响时间)


1
真好!您的编号有3)和4)交换了问题。鉴于差异不太大,看来唯一确定的结论是避免流。当然,这不仅取决于编译器(修订版),还取决于stdlib的实现(我认为coliru上的GCC之一)。
本杰明·班尼尔

34
不幸的是,该测试可能没有代表性。问题在于,由于不在l_czTempStr循环中包含声明,版本2和3不断重复使用同一缓冲区,而版本1std::string{""}每次都创建一个新缓冲区。您的基准测试表明,重用相同的缓冲区而不是分配/取消分配提供了5倍的提速(提速是显而易见的,该因素取决于片段的长度以及如果不保留所有内容,则会发生多少次重新分配-面前)。我不确定OP是否打算重新使用相同的缓冲区。
Matthieu M.

2
@MatthieuM .: +1好点。我更新了代码,以便在版本1中预先创建了初始字符串,但是内部operator+仍然有很多分配/取消分配的问题。
杰西·古德

3
您的stringstream基准测试包括(非常)缓慢的流构建时间。
拉普兹2014年

2
“看起来像2),+ =是赢家。” 我不确定您的结果与是否有任何统计差异.append()。它们也不应该:其中之一是根据另一个实现的。我不明白为什么它们会大不相同。
underscore_d

34

除了其他答案...

不久前,我对该问题进行了广泛的基准测试,得出的结论是,在所有用例中,最有效的解决方案(Linux x86 / x64 / ARM上的GCC 4.7和4.8)首先是reserve()具有足够空间来容纳所有内容的结果字符串。串联的字符串,然后只有append()它们(或使用operator +=(),没有区别)。

不幸的是,我似乎删除了该基准,因此您只能说我的话(但是,如果我的话还不够的话,您可以轻松地改用Mats Petersson的基准来自己验证)。

简而言之:

const string space = " ";
string result;
result.reserve(5 + space.size() + 5);
result += "hello";
result += space;
result += "world";

根据确切的用例(连接的字符串的数量,类型和大小),有时此方法到目前为止是最有效的,有时它可以与其他方法媲美,但绝不会更糟。


问题是,提前计算所需的总大小确实很麻烦,尤其是在混合字符串文字和时std::string(我相信上面的例子在这个问题上已经足够清楚了)。一旦您修改了这些文字之一或添加了要连接的另一个字符串,这种代码的可维护性就绝对可怕。

一种方法是用来sizeof计算文字的大小,但是恕我直言,它产生的杂乱无章,可维护性仍然很糟糕:

#define STR_HELLO "hello"
#define STR_WORLD "world"

const string space = " ";
string result;
result.reserve(sizeof(STR_HELLO)-1 + space.size() + sizeof(STR_WORLD)-1);
result += STR_HELLO;
result += space;
result += STR_WORLD;

可用的解决方案(C ++ 11,可变参数模板)

我终于找到了一套可变参数模板,可以根据需要有效地负责计算字符串的大小(例如,字符串文字的大小在编译时确定),reserve()然后将所有内容连接在一起。

希望是有用的:

namespace detail {

  template<typename>
  struct string_size_impl;

  template<size_t N>
  struct string_size_impl<const char[N]> {
    static constexpr size_t size(const char (&) [N]) { return N - 1; }
  };

  template<size_t N>
  struct string_size_impl<char[N]> {
    static size_t size(char (&s) [N]) { return N ? strlen(s) : 0; }
  };

  template<>
  struct string_size_impl<const char*> {
    static size_t size(const char* s) { return s ? strlen(s) : 0; }
  };

  template<>
  struct string_size_impl<char*> {
    static size_t size(char* s) { return s ? strlen(s) : 0; }
  };

  template<>
  struct string_size_impl<std::string> {
    static size_t size(const std::string& s) { return s.size(); }
  };

  template<typename String> size_t string_size(String&& s) {
    using noref_t = typename std::remove_reference<String>::type;
    using string_t = typename std::conditional<std::is_array<noref_t>::value,
                                              noref_t,
                                              typename std::remove_cv<noref_t>::type
                                              >::type;
    return string_size_impl<string_t>::size(s);
  }

  template<typename...>
  struct concatenate_impl;

  template<typename String>
  struct concatenate_impl<String> {
    static size_t size(String&& s) { return string_size(s); }
    static void concatenate(std::string& result, String&& s) { result += s; }
  };

  template<typename String, typename... Rest>
  struct concatenate_impl<String, Rest...> {
    static size_t size(String&& s, Rest&&... rest) {
      return string_size(s)
           + concatenate_impl<Rest...>::size(std::forward<Rest>(rest)...);
    }
    static void concatenate(std::string& result, String&& s, Rest&&... rest) {
      result += s;
      concatenate_impl<Rest...>::concatenate(result, std::forward<Rest>(rest)...);
    }
  };

} // namespace detail

template<typename... Strings>
std::string concatenate(Strings&&... strings) {
  std::string result;
  result.reserve(detail::concatenate_impl<Strings...>::size(std::forward<Strings>(strings)...));
  detail::concatenate_impl<Strings...>::concatenate(result, std::forward<Strings>(strings)...);
  return result;
}

就公共接口而言,唯一有趣的部分是最后一个template<typename... Strings> std::string concatenate(Strings&&... strings)模板。用法很简单:

int main() {
  const string space = " ";
  std::string result = concatenate("hello", space, "world");
  std::cout << result << std::endl;
}

启用优化功能后,任何体面的编译器都应该能够将concatenate调用扩展为与我第一个手动编写所有内容的示例相同的代码。就GCC 4.7和4.8而言,生成的代码和性能几乎相同。


我不明白在这里使用通用引用的原因。您能否解释一下,与常规(左值)对const的引用相比,它们可以提供什么优势?
Paul Groke 2015年

您是否已经完成了wstring的实现?看起来很简单。
KarlM'7

有或没有“保留”功能在我的测试中没有区别,有时“保留”功能更糟。

@Hao,也许是“ hello world”适合使用小字符串优化的std :: string,因此保留操作实际上什么也没做。
Emile Cormier

20

最糟糕的情况是使用普通的old strcat(或sprintf),因为strcat它使用了C字符串,必须对其进行“计数”才能找到结尾。对于长字符串,这是真正的性能受害者。C ++样式字符串要好得多,并且性能问题可能出在内存分配上,而不是长度计算上。但是话又说回来,字符串以几何形状增长(每次需要增长时都会增加一倍),所以并不是那么糟糕。

我非常怀疑上述所有方法最终都具有相同或至少非常相似的性能。如果有的话,stringstream由于支持格式化的开销,我希望它会比较慢-但我也怀疑它的边际。

由于这种事情很“有趣”,因此我将返回一个基准...

编辑:

请注意,这些结果适用于使用g ++ 4.6.3编译的运行x86-64 Linux的MY机器。其他操作系统,编译器和C ++运行时库的实现可能会有所不同。如果性能对您的应用程序很重要,请使用您使用的编译器在对您至关重要的系统上进行基准测试。

这是我编写的用于测试此代码的代码。它可能不是真实场景的完美代表,但我认为这是一个代表场景:

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <cstring>

using namespace std;

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

string build_string_1(const string &a, const string &b, const string &c)
{
    string out = a + b + c;
    return out;
}

string build_string_1a(const string &a, const string &b, const string &c)
{
    string out;
    out.resize(a.length()*3);
    out = a + b + c;
    return out;
}

string build_string_2(const string &a, const string &b, const string &c)
{
    string out = a;
    out += b;
    out += c;
    return out;
}

string build_string_3(const string &a, const string &b, const string &c)
{
    string out;
    out = a;
    out.append(b);
    out.append(c);
    return out;
}


string build_string_4(const string &a, const string &b, const string &c)
{
    stringstream ss;

    ss << a << b << c;
    return ss.str();
}


char *build_string_5(const char *a, const char *b, const char *c)
{
    char* out = new char[strlen(a) * 3+1];
    strcpy(out, a);
    strcat(out, b);
    strcat(out, c);
    return out;
}



template<typename T>
size_t len(T s)
{
    return s.length();
}

template<>
size_t len(char *s)
{
    return strlen(s);
}

template<>
size_t len(const char *s)
{
    return strlen(s);
}



void result(const char *name, unsigned long long t, const string& out)
{
    cout << left << setw(22) << name << " time:" << right << setw(10) <<  t;
    cout << "   (per character: " 
         << fixed << right << setw(8) << setprecision(2) << (double)t / len(out) << ")" << endl;
}

template<typename T>
void benchmark(const char name[], T (Func)(const T& a, const T& b, const T& c), const char *strings[])
{
    unsigned long long t;

    const T s1 = strings[0];
    const T s2 = strings[1];
    const T s3 = strings[2];
    t = rdtsc();
    T out = Func(s1, s2, s3);
    t = rdtsc() - t; 

    if (len(out) != len(s1) + len(s2) + len(s3))
    {
        cout << "Error: out is different length from inputs" << endl;
        cout << "Got `" << out << "` from `" << s1 << "` + `" << s2 << "` + `" << s3 << "`";
    }
    result(name, t, out);
}


void benchmark(const char name[], char* (Func)(const char* a, const char* b, const char* c), 
               const char *strings[])
{
    unsigned long long t;

    const char* s1 = strings[0];
    const char* s2 = strings[1];
    const char* s3 = strings[2];
    t = rdtsc();
    char *out = Func(s1, s2, s3);
    t = rdtsc() - t; 

    if (len(out) != len(s1) + len(s2) + len(s3))
    {
        cout << "Error: out is different length from inputs" << endl;
        cout << "Got `" << out << "` from `" << s1 << "` + `" << s2 << "` + `" << s3 << "`";
    }
    result(name, t, out);
    delete [] out;
}


#define BM(func, size) benchmark(#func " " #size, func, strings ## _ ## size)


#define BM_LOT(size) BM(build_string_1, size); \
    BM(build_string_1a, size); \
    BM(build_string_2, size); \
    BM(build_string_3, size); \
    BM(build_string_4, size); \
    BM(build_string_5, size);

int main()
{
    const char *strings_small[]  = { "Abc", "Def", "Ghi" };
    const char *strings_medium[] = { "abcdefghijklmnopqrstuvwxyz", 
                                     "defghijklmnopqrstuvwxyzabc", 
                                     "ghijklmnopqrstuvwxyzabcdef" };
    const char *strings_large[]   = 
        { "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
          "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", 

          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc"

          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" 
          "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc", 

          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
          "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef"
        };

    for(int i = 0; i < 5; i++)
    {
        BM_LOT(small);
        BM_LOT(medium);
        BM_LOT(large);
        cout << "---------------------------------------------" << endl;
    }
}

以下是一些代表性的结果:

build_string_1 small   time:      4075   (per character:   452.78)
build_string_1a small  time:      5384   (per character:   598.22)
build_string_2 small   time:      2669   (per character:   296.56)
build_string_3 small   time:      2427   (per character:   269.67)
build_string_4 small   time:     19380   (per character:  2153.33)
build_string_5 small   time:      6299   (per character:   699.89)
build_string_1 medium  time:      3983   (per character:    51.06)
build_string_1a medium time:      6970   (per character:    89.36)
build_string_2 medium  time:      4072   (per character:    52.21)
build_string_3 medium  time:      4000   (per character:    51.28)
build_string_4 medium  time:     19614   (per character:   251.46)
build_string_5 medium  time:      6304   (per character:    80.82)
build_string_1 large   time:      8491   (per character:     3.63)
build_string_1a large  time:      9563   (per character:     4.09)
build_string_2 large   time:      6154   (per character:     2.63)
build_string_3 large   time:      5992   (per character:     2.56)
build_string_4 large   time:     32450   (per character:    13.87)
build_string_5 large   time:     15768   (per character:     6.74)

相同的代码,以32位运行:

build_string_1 small   time:      4289   (per character:   476.56)
build_string_1a small  time:      5967   (per character:   663.00)
build_string_2 small   time:      3329   (per character:   369.89)
build_string_3 small   time:      3047   (per character:   338.56)
build_string_4 small   time:     22018   (per character:  2446.44)
build_string_5 small   time:      3026   (per character:   336.22)
build_string_1 medium  time:      4089   (per character:    52.42)
build_string_1a medium time:      8075   (per character:   103.53)
build_string_2 medium  time:      4569   (per character:    58.58)
build_string_3 medium  time:      4326   (per character:    55.46)
build_string_4 medium  time:     22751   (per character:   291.68)
build_string_5 medium  time:      2252   (per character:    28.87)
build_string_1 large   time:      8695   (per character:     3.72)
build_string_1a large  time:     12818   (per character:     5.48)
build_string_2 large   time:      8202   (per character:     3.51)
build_string_3 large   time:      8351   (per character:     3.57)
build_string_4 large   time:     38250   (per character:    16.35)
build_string_5 large   time:      8143   (per character:     3.48)

由此,我们可以得出以下结论:

  1. 最好的选择是一次(out.append()out +=)追加一点,而“链式”方法则相当接近。

  2. 预先分配字符串没有帮助。

  3. 使用stringstream是一个非常糟糕的主意(慢2-4倍之间)。

  4. char *用途new char[]。在调用函数中使用局部变量使其速度最快-对此进行比较有点不公平。

  5. 组合短字符串会有相当大的开销-仅复制数据每个字节最多应有一个周期(除非数据不适合高速缓存)。

编辑2

根据评论添加:

string build_string_1b(const string &a, const string &b, const string &c)
{
    return a + b + c;
}

string build_string_2a(const string &a, const string &b, const string &c)
{
    string out;
    out.reserve(a.length() * 3);
    out += a;
    out += b;
    out += c;
    return out;
}

得到以下结果:

build_string_1 small   time:      3845   (per character:   427.22)
build_string_1b small  time:      3165   (per character:   351.67)
build_string_2 small   time:      3176   (per character:   352.89)
build_string_2a small  time:      1904   (per character:   211.56)

build_string_1 large   time:      9056   (per character:     3.87)
build_string_1b large  time:      6414   (per character:     2.74)
build_string_2 large   time:      6417   (per character:     2.74)
build_string_2a large  time:      4179   (per character:     1.79)

(运行32位,但在64位上显示出非常相似的结果)。


1
基准不错,+ 1。实际上,关于1a(预分配字符串),您将丢弃预分配的缓冲区:的结果operator +()是一个临时变量,该临时变量被移入(或RVO)到临时out分配中,因此预分配是无用的。一个有趣的基准测试将是创建2a / 3a情况,其中您reserve()将结果字符串放在前面,然后将所有参数append()+=您的结果字符串作为参数。正如我在回答中解释的那样,我前段时间做了这样的基准测试,并得出结论,这确实是最有效的解决方案。
syam 2013年

我带了您的代码,并添加了一个build_string_1b刚刚完成的功能,return a + b + c;该功能在某些运行中是最快的功能(VS2012)。
高炉

1
仅仅涉及2a:当前,您有两个内存分配(a然后reserve是copy ),可以通过reserve在空字符串上使用,然后再使用 +=所有参数(这将为您分配单个内存分配reserve)来进一步改善。我自己编辑,但时间是针对您的机器的,所以我让您来做。;)
syam 2013年

@Syam:我实际上确实写了那个,但是我肯定已经复制了代码-结果就是我现在发布的代码。
Mats Petersson

11

与大多数微优化一样,您需要测量每个选项的效果,首先要通过测量确定这确实是一个值得优化的瓶颈。没有确切的答案。

append并且+=应该做完全一样的事情。

+从概念上讲效率较低,因为您正在创建和销毁临时对象。您的编译器可能无法或无法将其优化为与追加一样快。

reserve以总大小进行调用可能会减少所需的内存分配数量-它们可能是最大的瓶颈。

<<(大概在上stringstream)可能会更快,也可能不会更快;您需要进行衡量。如果您需要格式化非字符串类型,这很有用,但是在处理字符串时可能不会特别好或更坏。

CString 它的缺点是它不是可移植的,并且像我这样的Unix黑客无法告诉您它的优点是什么,也可能不是。


3

我决定使用用户Jesse Good提供的代码进行测试,并稍加修改以考虑到对Rapptz的观察,特别是在循环的每次迭代中构造了ostringstream的事实。因此,我添加了一些情况,其中一些情况是使用序列“ oss.str(“”); oss.clear() “清除的ostringstream

这是代码

#include <iostream>
#include <string>
#include <chrono>
#include <sstream>
#include <functional>


template <typename F> void time_measurement(F f, const std::string& comment)
{
    typedef std::chrono::high_resolution_clock clock;
    typedef std::chrono::duration<float, std::milli> mil;
    std::string r;
    auto t0 = clock::now();
    f(r);
    auto t1 = clock::now();
    std::cout << "\n-------------------------" << comment << "-------------------\n" <<r << '\n';
    std::cout << mil(t1-t0).count() << "ms\n";
    std::cout << "---------------------------------------------------------------------------\n";

}

inline void clear(std::ostringstream& x)
{
    x.str("");
    x.clear();
}

void test()
{
    std:: cout << std::endl << "----------------String Comparison---------------- " << std::endl;
    const int n=100000;
    {
        auto f=[](std::string& l_czTempStr)
        {
            std::string s1="Test data1";
            for (int i = 0; i < n; ++i)
            {
                l_czTempStr = s1 + "Test data2" + "Test data3";
            }
        };
        time_measurement(f, "string, plain addition");
   }

   {
        auto f=[](std::string& l_czTempStr)
        {
            for (int i = 0; i < n; ++i)
            {
                l_czTempStr =  "Test data1";
                l_czTempStr += "Test data2";
                l_czTempStr += "Test data3";
            }
        };
        time_measurement(f, "string, incremental");
    }

    {
         auto f=[](std::string& l_czTempStr)
         {
            for (int i = 0; i < n; ++i)
            {
                l_czTempStr =  "Test data1";
                l_czTempStr.append("Test data2");
                l_czTempStr.append("Test data3");
            }
         };
         time_measurement(f, "string, append");
     }

    {
         auto f=[](std::string& l_czTempStr)
         {
            for (int i = 0; i < n; ++i)
            {
                std::ostringstream oss;
                oss << "Test data1";
                oss << "Test data2";
                oss << "Test data3";
                l_czTempStr = oss.str();
            }
         };
         time_measurement(f, "oss, creation in each loop, incremental");
     }

    {
         auto f=[](std::string& l_czTempStr)
         {
            std::ostringstream oss;
            for (int i = 0; i < n; ++i)
            {
                oss.str("");
                oss.clear();
                oss << "Test data1";
                oss << "Test data2";
                oss << "Test data3";
            }
            l_czTempStr = oss.str();
         };
         time_measurement(f, "oss, 1 creation, incremental");
     }

    {
         auto f=[](std::string& l_czTempStr)
         {
            std::ostringstream oss;
            for (int i = 0; i < n; ++i)
            {
                oss.str("");
                oss.clear();
                oss << "Test data1" << "Test data2" << "Test data3";
            }
            l_czTempStr = oss.str();
         };
         time_measurement(f, "oss, 1 creation, plain addition");
     }

    {
         auto f=[](std::string& l_czTempStr)
         {
            std::ostringstream oss;
            for (int i = 0; i < n; ++i)
            {
                clear(oss);
                oss << "Test data1" << "Test data2" << "Test data3";
            }
            l_czTempStr = oss.str();
         };
         time_measurement(f, "oss, 1 creation, clearing calling inline function, plain addition");
     }


    {
         auto f=[](std::string& l_czTempStr)
         {
            for (int i = 0; i < n; ++i)
            {
                std::string x;
                x =  "Test data1";
                x.append("Test data2");
                x.append("Test data3");
                l_czTempStr=x;
            }
         };
         time_measurement(f, "string, creation in each loop");
     }

}

结果如下:

/*

g++ "qtcreator debug mode"
----------------String Comparison---------------- 

-------------------------string, plain addition-------------------
Test data1Test data2Test data3
11.8496ms
---------------------------------------------------------------------------

-------------------------string, incremental-------------------
Test data1Test data2Test data3
3.55597ms
---------------------------------------------------------------------------

-------------------------string, append-------------------
Test data1Test data2Test data3
3.53099ms
---------------------------------------------------------------------------

-------------------------oss, creation in each loop, incremental-------------------
Test data1Test data2Test data3
58.1577ms
---------------------------------------------------------------------------

-------------------------oss, 1 creation, incremental-------------------
Test data1Test data2Test data3
11.1069ms
---------------------------------------------------------------------------

-------------------------oss, 1 creation, plain addition-------------------
Test data1Test data2Test data3
10.9946ms
---------------------------------------------------------------------------

-------------------------oss, 1 creation, clearing calling inline function, plain addition-------------------
Test data1Test data2Test data3
10.9502ms
---------------------------------------------------------------------------

-------------------------string, creation in each loop-------------------
Test data1Test data2Test data3
9.97495ms
---------------------------------------------------------------------------


g++ "qtcreator release mode" (optimized)
----------------String Comparison----------------

-------------------------string, plain addition-------------------
Test data1Test data2Test data3
8.41622ms
---------------------------------------------------------------------------

-------------------------string, incremental-------------------
Test data1Test data2Test data3
2.55462ms
---------------------------------------------------------------------------

-------------------------string, append-------------------
Test data1Test data2Test data3
2.5154ms
---------------------------------------------------------------------------

-------------------------oss, creation in each loop, incremental-------------------
Test data1Test data2Test data3
54.3232ms
---------------------------------------------------------------------------

-------------------------oss, 1 creation, incremental-------------------
Test data1Test data2Test data3
8.71854ms
---------------------------------------------------------------------------

-------------------------oss, 1 creation, plain addition-------------------
Test data1Test data2Test data3
8.80526ms
---------------------------------------------------------------------------

-------------------------oss, 1 creation, clearing calling inline function, plain addition-------------------
Test data1Test data2Test data3
8.78186ms
---------------------------------------------------------------------------

-------------------------string, creation in each loop-------------------
Test data1Test data2Test data3
8.4034ms
---------------------------------------------------------------------------
*/

现在使用std :: string仍然更快,并且append仍然是最快的连接方式,但是ostringstream不再像以前那样可怕。


1

因此,由于该问题的答案已经很老了,我决定使用现代编译器更新其基准,并比较@ jesse-good和@syam的模板版本

这是组合的代码:

#include <iostream>
#include <string>
#include <chrono>
#include <sstream>
#include <vector>
#include <cstring>


#if VER==TEMPLATE
namespace detail {

  template<typename>
  struct string_size_impl;

  template<size_t N>
  struct string_size_impl<const char[N]> {
    static constexpr size_t size(const char (&) [N]) { return N - 1; }
  };

  template<size_t N>
  struct string_size_impl<char[N]> {
    static size_t size(char (&s) [N]) { return N ? strlen(s) : 0; }
  };

  template<>
  struct string_size_impl<const char*> {
    static size_t size(const char* s) { return s ? strlen(s) : 0; }
  };

  template<>
  struct string_size_impl<char*> {
    static size_t size(char* s) { return s ? strlen(s) : 0; }
  };

  template<>
  struct string_size_impl<std::string> {
    static size_t size(const std::string& s) { return s.size(); }
  };

  template<typename String> size_t string_size(String&& s) {
    using noref_t = typename std::remove_reference<String>::type;
    using string_t = typename std::conditional<std::is_array<noref_t>::value,
                                              noref_t,
                                              typename std::remove_cv<noref_t>::type
                                              >::type;
    return string_size_impl<string_t>::size(s);
  }

  template<typename...>
  struct concatenate_impl;

  template<typename String>
  struct concatenate_impl<String> {
    static size_t size(String&& s) { return string_size(s); }
    static void concatenate(std::string& result, String&& s) { result += s; }
  };

  template<typename String, typename... Rest>
  struct concatenate_impl<String, Rest...> {
    static size_t size(String&& s, Rest&&... rest) {
      return string_size(s)
           + concatenate_impl<Rest...>::size(std::forward<Rest>(rest)...);
    }
    static void concatenate(std::string& result, String&& s, Rest&&... rest) {
      result += s;
      concatenate_impl<Rest...>::concatenate(result, std::forward<Rest>(rest)...);
    }
  };

} // namespace detail

template<typename... Strings>
std::string concatenate(Strings&&... strings) {
  std::string result;
  result.reserve(detail::concatenate_impl<Strings...>::size(std::forward<Strings>(strings)...));
  detail::concatenate_impl<Strings...>::concatenate(result, std::forward<Strings>(strings)...);
  return result;
}

#endif

int main ()
{
typedef std::chrono::high_resolution_clock clock;
typedef std::chrono::duration<float, std::milli> ms;
std::string l_czTempStr;


std::string s1="Test data1";


auto t0 = clock::now();
#if VER==PLUS
for (int i = 0; i < 100000; ++i)
{
    l_czTempStr = s1 + "Test data2" + "Test data3";
}
#elif VER==PLUS_EQ
for (int i = 0; i < 100000; ++i)
{
    l_czTempStr =  "Test data1"; 
    l_czTempStr += "Test data2";
    l_czTempStr += "Test data3";
}
#elif VER==APPEND
for (int i = 0; i < 100000; ++i)
{
    l_czTempStr =  "Test data1"; 
    l_czTempStr.append("Test data2");
    l_czTempStr.append("Test data3");
}
#elif VER==STRSTREAM
for (int i = 0; i < 100000; ++i)
{
    std::ostringstream oss;
    oss << "Test data1";
    oss << "Test data2";
    oss << "Test data3";
    l_czTempStr = oss.str();
}
#elif VER=TEMPLATE
for (int i = 0; i < 100000; ++i)
{
    l_czTempStr = concatenate(s1, "Test data2", "Test data3");
}
#endif

#define STR_(x) #x
#define STR(x) STR_(x)

auto t1 = clock::now();
//std::cout << l_czTempStr << '\n';
std::cout << STR(VER) ": " << ms(t1-t0).count() << "ms\n";
}

测试说明:

for ARGTYPE in PLUS PLUS_EQ APPEND STRSTREAM TEMPLATE; do for i in `seq 4` ; do clang++ -std=c++11 -O3 -DVER=$ARGTYPE -Wall -pthread -pedantic main.cpp && ./a.out ; rm ./a.out ; done; done

和结果(通过电子表格进行处理以显示平均时间):

PLUS       23.5792   
PLUS       23.3812   
PLUS       35.1806   
PLUS       15.9394   24.5201
PLUS_EQ    15.737    
PLUS_EQ    15.3353   
PLUS_EQ    10.7764   
PLUS_EQ    25.245    16.773425
APPEND     22.954    
APPEND     16.9031   
APPEND     10.336    
APPEND     19.1348   17.331975
STRSTREAM  10.2063   
STRSTREAM  10.7765   
STRSTREAM  13.262    
STRSTREAM  22.3557   14.150125
TEMPLATE   16.6531   
TEMPLATE   16.629    
TEMPLATE   22.1885   
TEMPLATE   16.9288   18.09985

令人惊讶的是strstream,它似乎从C ++ 11和更高版本的改进中受益匪浅。由于引入了移动语义,可能删除了必要的分配会产生一些影响。

您可以在coliru上自行测试

编辑:我已经更新了对coliru的测试,以使用g ++-4.8:http ://coliru.stacked-crooked.com/a/593dcfe54e70e409 。图表结果如下: g ++-4.8测试结果

(说明-“统计平均值”是指除两个极端值(一个最小值和一个最大值)以外的所有值的平均值)


1
仍然值得注意的是YMMV,您应该执行自己的基准测试,因为我针对启用了其他各种标志的g ++ 4.8.5进行编译,导致流性能大约是+ =的两倍,并且附加和与+相比提高了50% 。
devyndraen

@devyndraen-100%正确-每个方面都很重要。但是2x仍然比Jesse Good最初报告的〜20x好得多。这是我使用g ++-7 BTW的结果(总计-4次运行的总和):PLUS:18.49352,PLUS_EQ:19.03214,APPEND:18.70595,STRSTREAM:19.17043,模板:21.98324
yatsek

尝试在coliru上重复使用g ++-4.8进行测试:coliru.stacked-crooked.com/a/593dcfe54e70e409,请在此处绘制图形:i.imgur.com/w4TXPO3.png(统计平均值是所有最小值和最大值的平均值。值)
yatsek

0

有一些重要的参数,可能会对确定“最优化的方式”产生影响。其中一些是-字符串/内容大小,操作数,编译器优化等。

在大多数情况下,string::operator+=似乎效果最好。但是,有时在某些编译器上,也可以观察到ostringstream::operator<<效果最佳(例如-MingW g ++ 3.2.3、1.8 GHz单处理器Dell PC)。当编译器上下文出现时,影响编译器的主要是优化。还要提到的stringstreams是,与简单字符串相比,它们是复杂的对象,因此增加了开销。

有关更多信息讨论,请参阅文章

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.