C++中高效的字符串拼接

130

我听到有一些人对std :: string中的“+”运算符表示担忧,并提出各种加速字符串连接的解决方案。这些方法是否真的必要?如果是,什么是在C ++ 中拼接字符串的最佳方式?


17
基本上,“+”不是连接运算符(因为它会生成一个新的字符串)。使用“+=”进行连接。 - Martin York
5
自从 C++11 以来,有一个重要的点:如果将 operator+ 的某个操作数传递为右值引用,则该运算符可以修改其中一个操作数并通过移动其返回它。例如 libstdc++ 中的实现方式。因此,当使用临时对象调用 operator+ 时,它几乎可以达到与其他方式相同的性能 - 这可能是默认情况下选择它的论点,出于可读性的考虑,除非有基准测试表明它是瓶颈。但是,一个标准化的可变参数 append() 不仅最优,而且更加易读... - underscore_d
14个回答

2

如果你预先分配(保留)结果字符串中的空间,那么性能最佳。

Original Answer翻译成"最初的回答"

template<typename... Args>
std::string concat(Args const&... args)
{
    size_t len = 0;
    for (auto s : {args...})  len += strlen(s);

    std::string result;
    result.reserve(len);    // <--- preallocate result
    for (auto s : {args...})  result += s;
    return result;
}

使用方法:

std::string merged = concat("This ", "is ", "a ", "test!");

1
一个简单的字符数组,封装在一个类中,该类跟踪数组大小和分配字节数量是最快的。 技巧在于一开始只进行一次大型分配。 at

https://github.com/pedro-vicente/table-string

基准测试

对于Visual Studio 2015,x86调试构建,相对于C++ std::string有显著的改进。

| API                   | Seconds           
| ----------------------|----| 
| SDS                   | 19 |  
| std::string           | 11 |  
| std::string (reserve) | 9  |  
| table_str_t           | 1  |  

1
OP对如何高效地连接std::string感兴趣。他们并不要求使用其他字符串类。 - underscore_d

1

您可以尝试为每个项目设置内存预留来解决此问题:

namespace {
template<class C>
constexpr auto size(const C& c) -> decltype(c.size()) {
  return static_cast<std::size_t>(c.size());
}

constexpr std::size_t size(const char* string) {
  std::size_t size = 0;
  while (*(string + size) != '\0') {
    ++size;
  }
  return size;
}

template<class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept {
  return N;
}
}

template<typename... Args>
std::string concatStrings(Args&&... args) {
  auto s = (size(args) + ...);
  std::string result;
  result.reserve(s);
  return (result.append(std::forward<Args>(args)), ...);
}

0

基准测试使用Visual Studio C/C++ 17.2.5和Boost 1.79.0在Ryzen 5600x上进行:

n iter = 10
n parts = 10000000
total string result length = 70000000
Boost join: 00:00:02.105006
std::string append (Reserve): 00:00:00.485498
std::string append (simple): 00:00:00.679999
Note: times are cumulative sums over all iterations.

结论:就性能而言,boost实现并不是很好。使用std::string的reserve对性能影响不大,除非最终字符串长度至少为数十兆字节。
在实践中,简单的append(不使用reserve)甚至可能更快,因为基准测试使用了已初始化的字符串部分向量。在实践中,该向量通常仅对于reserve/boost join变体是必要的,因此对它们来说是额外的性能惩罚。
另一次运行:
n iter = 100
n parts = 1000000
total string result length = 6000000
Boost join: 00:00:01.953999
std::string append (Reserve): 00:00:00.535502
std::string append (simple): 00:00:00.679002
Note: times are cumulative sums over all iterations.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接