std :: string_view究竟比const std :: string&更快?

Pat*_*ryk 195 c++ string string-view c++17

std::string_view已经使它成为C++ 17,并且它被广泛推荐使用它代替const std::string&.

其中一个原因是表现.

有人可以解释与用作参数类型相比,究竟 std::string_view是什么/将会更快const std::string&?(假设在被叫方中没有副本)

Yak*_*ont 190

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

首先,std::string const&要求数据在a std::string,而不是原始C数组中,char const*由C API返回,std::vector<char>由某些反序列化引擎生成,等等.避免格式转换避免复制字节,并且(如果字符串长于特定std::string实现的SBO 1 )避免了内存分配.

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] );
}
Run Code Online (Sandbox Code Playgroud)

在这种string_view情况下没有进行任何分配,但是如果用foo一个std::string const&而不是一个string_view.

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

相反,如果将其解析为std::string_views,则节点将引用原始数据.这可以在解析过程中节省数百万个分配并减少内存需求.

你可以获得的加速非常荒谬.

这是一个极端的情况,但其他"获得一个子串并使用它"的情况下也可以产生不错的加速string_view.

决定的一个重要部分是你通过使用而失去的东西std::string_view.它并不多,但它是一些东西.

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

如果std::string_view有一个标志声明它是否为空终止(或更高级的东西)它甚至会删除使用a的最后一个原因std::string const&.

有一种情况下,a std::string取而代之的const&是a std::string_view.如果您需要在通话后无限期拥有该字符串的副本,则取值按钮是有效的.您将处于SBO案例中(并且没有分配,只需要几个字符副本来复制它),或者您将能够堆分配的缓冲区移动到本地std::string.有两个重载std::string&&,std::string_view可能会更快,但只是稍微,它会导致适度的代码膨胀(这可能会花费你所有的速度增益).


¹小缓冲区优化

²实际使用案例.

  • SBO 听起来很奇怪。我一直听说 SSO(小字符串优化) (4认同)
  • 您也会失去所有权。仅在返回字符串并且*可能*必须是缓冲区的子字符串之外的任何内容(这可以保证生存足够长的时间)时,这才有意义。实际上,所有权的丧失是两刃的武器。 (2认同)
  • 附带说明一下,“char const*const* argv”只是显示了 C++ 代码中的愚蠢和歧义程度...... (2认同)

Pav*_*dov 55

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

std::string str{"foobar"};
auto bar = str.substr(3);
assert(bar == "bar");
Run Code Online (Sandbox Code Playgroud)

使用std :: string_view:

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

更新:

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

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
Run Code Online (Sandbox Code Playgroud)

结果

(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
Run Code Online (Sandbox Code Playgroud)

  • 很高兴您提供了一个实际的基准。这确实显示了在相关用例中可以获得什么。 (4认同)

Mat*_* M. 44

主要有两个原因:

  • string_view 是现有缓冲区中的片,它不需要内存分配
  • string_view 按值传递,而不是通过引用传递

切片的优点是多个:

  • 您可以在分配char const*char[]不分配新缓冲区的情况下使用它
  • 您可以将多个切片和子切片放入现有缓冲区而无需分配
  • substring是O(1),而不是O(N)
  • ...

全面更好,更稳定的表现.


传递值也比通过引用传递优势,因为别名.

具体来说,当您有std::string const&参数时,无法保证不会修改引用字符串.因此,编译器必须在每次调用后重新获取字符串的内容为opaque方法(指向数据,长度,...).

另一方面,当传递一个string_viewby值时,编译器可以静态地确定没有其他代码可以修改现在堆栈(或寄存器)中的长度和数据指针.因此,它可以跨函数调用"缓存"它们.


jua*_*nza 36

它可以做的一件事是避免std::string在从空终止字符串进行隐式转换的情况下构造对象:

void foo(const std::string& s);

...

foo("hello, world!"); // std::string object created, possible dynamic allocation.
char msg[] = "good morning!";
foo(msg); // std::string object created, possible dynamic allocation.
Run Code Online (Sandbox Code Playgroud)

  • 值得一提的是`const std :: string str {"goodbye!"}; foo(str);`可能*不会*使用string_view比使用string&更快 (12认同)
  • `string_view` 会不会很慢,因为它必须复制两个指针而不是 `const string&amp;` 中的一个指针? (2认同)

n.c*_*lou 7

std::string_view基本上只是一个包装const char*.并且传递const char*意味着与传递const string*(或const string&)相比,系统中将少一个指针,因为它string*意味着:

string* -> char* -> char[]
           |   string    |
Run Code Online (Sandbox Code Playgroud)

显然,为了传递const参数,第一个指针是多余的.

ps然而,std::string_view和之间的一个const char*缺点是,string_views不需要以空值终止(它们具有内置大小),这允许对较长字符串进行随机就地拼接.

  • @mlvljr没有人传递`std :: string const*`.而这个图表是难以理解的.@ n.caillou:你自己的评论已经比答案更准确了.这使得`string_view`不仅仅是"花哨的`char const*`" - 它真的非常明显. (6认同)
  • 什么是downvotes?`std :: string_view`只是花哨的`const char*`s,句号.GCC实现它们:`class basic_string_view {const _CharT*_M_str; size_t _M_len;}` (4认同)
  • 只需达到65K代表(从您当前的65),这将是公认的答案(向货物崇拜人群挥手致意):) (4认同)
  • @sehe您确实了解,从优化或执行的角度来看,`std :: string const *`和`std :: string const&`是相同的,不是吗? (2认同)