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


221

std::string_view 已使用C ++ 17,因此广泛建议使用它代替 const std::string&

原因之一是性能。

有人能解释一下究竟 std::string_view是/将快于const std::string&作为参数类型时?(假设未在被调用方中进行任何复制)


7
std::string_view只是(char * begin,char * end)对的抽象。在制作std::string不必要的副本时使用它。
QuestionC

我认为问题不在于哪一个更快,而是何时使用它们。如果我需要对字符串进行某种操作,并且它不是永久性的和/或保持原始值,则string_view是完美的,因为我不需要为其复制字符串。但是,如果我仅需要使用例如string :: find检查字符串上的某些内容,则引用会更好。
TheArquitect

@QuestionC你使用它时,你不想让你的API来限制std::string(string_view可以接受原始数组,向量,std::basic_string<>使用非默认分配器等等,等等等等哦,和其他string_views明显)
sehe

Answers:


213

std::string_view 在某些情况下更快。

首先,std::string const&要求数据位于std::string而不是原始C数组中,而不是char const*C API返回的数据,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] );
}

在这种string_view情况下,不会进行任何分配,但是如果使用fooa std::string const&而不是astring_view

第二个很大的原因是它允许使用子字符串而不使用副本。假设您正在解析2 GB的json字符串(!)²。如果将其解析为std::string,则每个这样的解析节点将在其中存储节点名称或值的副本原始数据从2 gb字符串到本地节点。

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

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

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

决定的重要部分是您因使用而损失了什么 std::string_view。数量不多,但确实如此。

您将丢失隐式空终止,仅此而已。因此,如果将相同的字符串传递给3个函数,而所有这些函数都需要一个空终止符,std::string则明智的做法是将其转换为一次。因此,如果已知您的代码需要空终止符,并且您不希望从C样式的源缓冲区等提供的字符串,则可以使用std::string const&。否则采取行动std::string_view

如果std::string_view有一个标志说明它是否为null终止(或更奇特的东西),它将删除使用的最后一个原因std::string const&

在某些情况下,对a std::string采取不采取行动const&是最优的std::string_view。如果您需要在调用后无限期拥有该字符串的副本,则按值取值是有效的。您要么处于SBO情况(没有分配,仅复制几个字符),要么就可以堆分配的缓冲区移动到local std::string。具有两个重载,std::string&&并且std::string_view可能更快,但是只有一点点,这将导致适度的代码膨胀(这可能会浪费您所有的速度提升)。


¹小缓冲区优化

²实际用例。


8
您也会失去所有权。仅在返回字符串时才有意义,并且字符串可能必须是缓冲区的子字符串以外的其他内容,以保证可以生存足够长的时间。实际上,所有权的丧失是两刃的武器。
Deduplicator

SBO听起来很奇怪。我一直都听说过SSO(小字符串优化)
phuclv

@phu当然;但是字符串并不是您使用技巧的唯一内容。
Yakk-Adam Nevraumont

@phuclv SSO只是SBO的一种特殊情况,代表小型缓冲区优化。替代术语是小数据选择。小物件选择。,或选择小尺寸。
Daniel Langr

59

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");

更新:

我写了一个非常简单的基准来添加一些实数。我使用了很棒的Google基准测试库。基准功能包括:

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

2
您提供了一个实际的基准真是太好了。这确实显示了在相关用例中可以获得什么。
丹尼尔·卡米尔·科扎尔

1
@DanielKamilKozar感谢您的反馈。我还认为基准很有价值,有时它们会改变一切。
帕维尔·达维多夫

47

主要有两个原因:

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

切片的优点有很多:

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

始终具有更好,更一致的性能。


与别名一样,按值传递也比按引用传递具有优势。

具体来说,当您有 std::string const&参数时,无法保证不会修改参考字符串。结果,在每次调用不透明方法(指向数据,长度等)之后,编译器必须重新获取字符串的内容。

另一方面,当string_view按值传递值时,编译器可以静态确定当前没有其他代码可以修改堆栈(或寄存器)中的长度和数据指针。结果,它可以跨函数调用“缓存”它们。


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.

12
也许值得一提的是,使用string_view const std::string str{"goodbye!"}; foo(str);可能不会比使用string&快
Martin Bonner支持Monica

1
会不会string_view很慢,因为它必须复制两个指针而不是一个指针const string&
balki

9

std::string_view基本上只是一个包装纸const char*。并且passing const char*表示与passing const string*(或const string&)相比,系统中的指针要少string*一些,因为这意味着:

string* -> char* -> char[]
           |   string    |

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

psstd::string_view和之间的一个const char*重大区别是,string_views不需要以空值结尾(它们具有内置的大小),并且允许对更长的字符串进行随机就地拼接。


4
下票是什么?std::string_views只是花哨的const char*s,时期。GCC的实施方式如下:class basic_string_view {const _CharT* _M_str; size_t _M_len;}
n.caillou

4
只需达到65K代表(从您当前的65岁起),这便是可以接受的答案(向
热衷

7
@mlvljr没有人通过std::string const*。该图是难以理解的。//@n.caillou:您自己的评论已经比答案更准确。这string_view远远超过了“花哨char const*”-这确实很明显。
sehe

@sehe我可能是没人,没有问题(例如,将指针(或引用)传递给const字符串,为什么不呢?):)
mlvljr

2
@sehe你也明白,从优化或执行的角度来看,std::string const*std::string const&是一样的,不是吗?
n.caillou
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.