std::string_view 到底比 const std::string& 快多少?

新手上路,请多包涵

std::string_view 已进入 C++17,并且广泛推荐使用它来代替 const std::string&

原因之一是性能。

有人能解释 一下 std::string_view 在用作参数类型时比 const std::string& 快吗? (假设在被调用者中没有复制)

原文由 Patryk 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.7k
2 个回答

std::string_view 在少数情况下更快。

First, std::string const& requires the data to be in a std::string , and not a raw C array, a char const* returned by a C API, a std::vector<char> 由一些反序列化引擎等产生。避免的格式转换避免了复制字节,并且(如果字符串比特定 std::string 实现的 SBO¹ 长)避免了内存分配。

 void foo( std::string_view bob ) {
  std::cout << bob << "\n";
}
int main(int argc, char const*const* argv) {
  foo( "This is a string long enough to avoid the std::string SBO" );
  if (argc > 1)
    foo( argv[1] );
}

No allocations are done in the string_view case, but there would be if foo took a std::string const& instead of a string_view .

第二个真正重要的原因是它允许在没有副本的情况下使用子字符串。假设您正在解析一个 2 GB 的 json 字符串 (!)²。如果将其解析为 std::string ,则每个存储节点名称或值的此类解析节点会将原始数据从 2 gb 字符串 复制 到本地节点。

相反,如果将其解析为 std::string_view s,则节点 引用 原始数据。这可以在解析期间节省数百万次分配并将内存需求减半。

您可以获得的加速简直是荒谬的。

这是一个极端情况,但其他“获取子字符串并使用它”的情况也可以通过 string_view 产生不错的加速。

决定的一个重要部分是使用 std::string_view 会失去什么。这不是很多,但它是一些东西。

你失去了隐含的空终止,就是这样。因此,如果将相同的字符串传递给所有需要空终止符的 3 个函数,则转换为 std::string 一次可能是明智的。因此,如果您的代码已知需要一个空终止符,并且您不希望从 C 样式源缓冲区等提供字符串,则可能采用 std::string const& 。否则采取 std::string_view

如果 std::string_view 有一个标志表明它是否为空终止(或更高级的东西),它甚至会删除使用 std::string const& 的最后一个理由。

在某些情况下,采用 std::string 没有 const& 优于 std::string_view 。如果您需要在调用后无限期地拥有字符串的副本,则按值获取是有效的。您将处于 SBO 的情况下(并且没有分配,只需几个字符副本即可复制它),或者您将能够 堆分配的缓冲区移动到本地 std::string 。有两个重载 std::string&&std::string_view 可能会更快,但只是轻微的,它会导致适度的代码膨胀(这可能会让你失去所有的速度提升)。


¹ 小缓冲区优化

² 实际用例。

原文由 Yakk - Adam Nevraumont 发布,翻译遵循 CC BY-SA 3.0 许可协议

string_view 提高性能的一种方法是它允许轻松删除前缀和后缀。在后台,string_view 可以将前缀大小添加到指向某个字符串缓冲区的指针,或者从字节计数器中减去后缀大小,这通常很快。另一方面,当您执行 substr 之类的操作时,std::string 必须复制其字节(这样您将获得一个拥有其缓冲区的新字符串,但在许多情况下您只想获取原始字符串的一部分而不复制)。例子:

 std::string str{"foobar"};
auto bar = str.substr(3);
assert(bar == "bar");

使用 std::string_view:

 std::string str{"foobar"};
std::string_view bar{str.c_str(), str.size()};
bar.remove_prefix(3);
assert(bar == "bar");

更新:

我写了一个非常简单的基准来添加一些实数。我使用了很棒的 谷歌基准库。基准函数是:

 string remove_prefix(const string &str) {
  return str.substr(3);
}
string_view remove_prefix(string_view str) {
  str.remove_prefix(3);
  return str;
}
static void BM_remove_prefix_string(benchmark::State& state) {
  std::string example{"asfaghdfgsghasfasg3423rfgasdg"};
  while (state.KeepRunning()) {
    auto res = remove_prefix(example);
    // auto res = remove_prefix(string_view(example)); for string_view
    if (res != "aghdfgsghasfasg3423rfgasdg") {
      throw std::runtime_error("bad op");
    }
  }
}
// BM_remove_prefix_string_view is similar, I skipped it to keep the post short

结果

(x86_64 linux,gcc 6.2,“ -O3 -DNDEBUG ”):

 Benchmark                             Time           CPU Iterations
-------------------------------------------------------------------
BM_remove_prefix_string              90 ns         90 ns    7740626
BM_remove_prefix_string_view          6 ns          6 ns  120468514

原文由 Pavel Davydov 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题