字符串中最优化的连接方式
Posted
技术标签:
【中文标题】字符串中最优化的连接方式【英文标题】:Most optimized way of concatenation in strings 【发布时间】:2013-09-24 09:30:41 【问题描述】:我们每天都会遇到很多情况,我们必须在代码中进行繁琐且非常多的字符串操作。我们都知道字符串操作是昂贵的操作。我想知道可用版本中哪个最便宜。
最常见的操作是串联(这是我们可以在一定程度上控制的)。在 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()
另外,使用 CString 比使用 std::string 有什么好处吗?
【问题讨论】:
为什么不能测量?无论如何,stringstream
是为此用例构建的,string
不是。所以从stringstream
开始可能是一个不错的选择。
1.不合法,ITYM l_czTempStr = std::string("Test data1") + "Test data2" + "Test data3";
。除此之外,答案是对不同的技术进行计时。变量太多,无法回答这个问题。答案取决于您正在使用的字符串的数量和长度,以及您正在编译的平台以及您正在编译的平台。
真的是瓶颈吗?然后对其进行基准测试。一般来说,最快的方法是在追加任何数据之前为所有数据预分配足够的空间,并避免使用临时对象(+
创建一个新对象,在 C++11 中有一些特殊情况)。但是除非你需要,否则不要优化它,否则你的代码将无法阅读。
@MagnusHoff 你搞错了。 std::ostringstream
专为格式化而设计,通常仅在需要格式化时使用。他的所有数据都是字符串,所以std::string
和串联是首选方案。
附带说明:对于很长的字符串,使用Rope 代替字符串可能是个好主意。
【参考方案1】:
这是一个小测试套件:
#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.61773ms
-DVER=3
6.7855ms
-DVER=4
102.015 毫秒
看起来2)
,+=
是赢家。
(同时使用和不使用-pthread
进行编译似乎会影响时间)
【讨论】:
不错!您的编号有 3) 和 4) 交换了问题。鉴于没有太大的差异,看起来唯一确定的结论是避免流。这当然不仅取决于编译器(修订版),还取决于 stdlib 实现(我认为是 GCC 在 coliru 上的一个)。 很遗憾,该测试可能不具有代表性。问题在于,通过在循环中不包含l_czTempStr
声明,版本 2 和 3 一遍又一遍地重用相同的缓冲区,而版本 1 每次都会创建一个新的缓冲区 std::string""
。您的基准测试表明,重用相同的缓冲区而不是分配/取消分配提供了 5 倍的加速(加速很明显,该因素取决于片段的长度以及如果您不保留所有内容,则会发生多少重新分配-正面)。不过,我不确定 OP 是否打算重新使用相同的缓冲区。
@MatthieuM.:+1 好点。我更新了代码,因此在版本 1 中预先创建了初始字符串,但是 operator+
在内部仍然存在大量分配/解除分配问题。
您的stringstream
基准包括(令人难以置信的)缓慢的流构建时间。
“看起来像 2),+= 是赢家。”我不确定您的结果与 .append()
有什么统计差异。他们也不应该:其中一个是根据另一个来实现的。我不明白为什么它们会有显着不同。【参考方案2】:
除了其他答案...
前段时间我对这个问题进行了广泛的基准测试,得出的结论是所有用例中最有效的解决方案(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 的普通(左值)引用有什么优势吗? 你有没有完成这个的 wstring 实现?看起来很简单。 有或没有“保留”功能在我的测试中显示没有差异,有时“保留”功能更差。 @Hao,也许“hello world”适合使用小字符串优化的std::string,因此保留操作实际上什么都不做。【参考方案3】:最糟糕的情况是使用普通的旧strcat
(或sprintf
),因为strcat
需要一个C 字符串,并且必须“计数”才能找到结尾。对于长字符串,这是一个真正的性能受害者。 C++ 风格的字符串要好得多,性能问题可能与内存分配有关,而不是计算长度。但是话又说回来,字符串以几何方式增长(每次需要增长时加倍),所以它并不那么糟糕。
我非常怀疑上述所有方法最终都具有相同或至少非常相似的性能。如果有的话,我希望stringstream
会更慢,因为支持格式的开销 - 但我也怀疑它是微不足道的。
由于这种事情很“有趣”,我会回来做一个基准......
编辑:
请注意,这些结果适用于我的机器,运行 x86-64 Linux,使用 g++ 4.6.3 编译。其他操作系统、编译器和 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)
由此我们可以得出结论:
最好的选择是一次追加一点(out.append()
或out +=
),“链式”方法相当接近。
预分配字符串没有帮助。
使用 stringstream
是个很糟糕的主意(慢 2-4 倍)。
char *
使用 new char[]
。在调用函数中使用局部变量使其最快 - 但比较它有点不公平。
在组合短字符串时有相当多的开销 - 仅复制数据应该最多每个字节一个周期 [除非数据不适合缓存]。
edit2
根据 cmets 添加:
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。关于 1a (预分配字符串)实际上您正在丢弃预分配的缓冲区:operator +()
的结果是一个临时的,它被移动(或 RVO'd)到 out
所以预分配是没用的。一个有趣的基准是创建 2a / 3a 案例,其中您 reserve()
前面的结果字符串,然后 append()
或 +=
为您的结果的所有参数细绳。正如我在回答中解释的那样,我前段时间做过这样的基准测试,并得出结论,这确实是最有效的解决方案。
我使用了你的代码并添加了一个 build_string_1b
函数,该函数刚刚执行了 return a + b + c;
,结果证明它是某些运行中最快的函数 (VS2012)。
只是对 2a 的吹毛求疵:目前你有两个内存分配(复制a
然后reserve
),这可以通过在一个空的上使用reserve
来进一步改进字符串,然后只有+=
所有参数(这给你一个单一的内存分配,reserve
)。我会自己编辑,但时间是为你的机器准备的,所以我会让你做。 ;)
@Syam:我确实最终写了那个,但我一定是复制了错误的代码 - 结果是我现在发布的代码。【参考方案4】:
与大多数微优化一样,您需要衡量每个选项的效果,首先通过衡量确定这确实是一个值得优化的瓶颈。没有确定的答案。
append
和 +=
应该做同样的事情。
+
在概念上效率较低,因为您正在创建和销毁临时对象。您的编译器可能会也可能无法将其优化为与追加一样快。
使用总大小调用 reserve
可能会减少所需的内存分配数量 - 它们可能是最大的瓶颈。
<<
(大概在stringstream
上)可能会也可能不会更快;你需要测量它。如果您需要格式化非字符串类型,这很有用,但在处理字符串方面可能不会特别好或特别差。
CString
的缺点是不可移植,而且像我这样的 Unix 黑客无法告诉你它的优点可能是什么,也可能不是。
【讨论】:
【参考方案5】:我决定使用用户 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 仍然更快,追加仍然是最快的连接方式,但 ostringstream 不再像以前那样可怕。
【讨论】:
【参考方案6】:因此,由于这个问题的公认答案相当陈旧,我决定用现代编译器更新它的基准,并比较@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。图中的结果:
(解释 - “stat.average”是指除两个极端值之外的所有值的平均值 - 一个最小值和一个最大值)
【讨论】:
仍然值得注意的是,YMMV 和您应该执行自己的基准测试,因为我针对 g++ 4.8.5 编译并启用了各种其他标志导致流性能大约是 += 和附加的两倍与 + 相比增加了 50%。 @devyndraen - 100% 正确 - 每个方面都很重要。然而,2x 仍然比 Jesse Good 最初报告的 ~20x 好得多。这是我的(合并 - 4 次运行的总和)结果与 g++-7 BTW:PLUS:18.49352,PLUS_EQ:19.03214,APPEND:18.70595,STREAM:19.17043,TEMPLATE:21.98324 尝试使用 g++-4.8 在 coliru 上重复测试:coliru.stacked-crooked.com/a/593dcfe54e70e409,此处为图表:i.imgur.com/w4TXPO3.png(统计平均值是所有负最小值和最大值的平均值)【参考方案7】:使用 C++17 这个简单的解决方案应该有非常好的性能,在大多数情况下可以与@syam 的模板重解决方案相媲美。在某些情况下它会更快,避免不必要的strlen
调用。
#include <string>
#include <string_view>
template <typename... T>
std::string concat(T ...args)
std::string result;
std::string_view views[] args... ;
std::string::size_type full_size = 0;
for (auto sub_view : views)
full_size += sub_view.size();
result.reserve(full_size);
for (auto sub_view : views)
result.append(sub_view);
return result;
这里有一点冗余——我们真的不需要存储 string_views,只需要存储参数的长度。但是,开销可以忽略不计,并且使代码清晰明了。
std::string_view
s 存储参数的长度。因此,将它们附加到 std::string
可能比附加到 char*
s 更快。此外,std::string_view
使用 std::char_traits
进行长度计算,在某些实现中,可以在编译时计算编译时已知的参数。对于strlen
这样的 C 调用,通常无法执行此优化。
【讨论】:
考虑使用const T& ...args
而不是T ...args
以避免在传递左值时不必要的参数副本。【参考方案8】:
有一些重要的参数对决定“最优化的方式”有潜在的影响。其中一些是 - 字符串/内容大小、操作数、编译器优化等。
在大多数情况下,string::operator+=
似乎工作得最好。但是有时,在某些编译器上,也观察到 ostringstream::operator<<
效果最好 [例如 - MingW g++ 3.2.3、1.8 GHz 单处理器戴尔 PC]。当编译器上下文出现时,主要是编译器的优化会产生影响。还要提一下,与简单字符串相比,stringstreams
是复杂的对象,因此会增加开销。
欲了解更多信息 - discussion、article。
【讨论】:
以上是关于字符串中最优化的连接方式的主要内容,如果未能解决你的问题,请参考以下文章