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

std::string_view使它成为C ++ 17,广泛推荐使用它来代替const std::string&

其中一个原因是性能。

有人可以解释如何确切地说, std::string_view是/将比const std::string&当用作参数types更快吗? (让我们假设在被调用者中没有任何副本)

std::string_view在less数情况下速度更快。

首先, std::string const&要求数据在std::string ,而不是原始的C数组,由C API返回的char const* ,由某个反序列化引擎生成的std::vector<char>等等。避免的格式转换避免了复制字节,并且(如果string比特定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情况下没有分配,但是如果foo使用std::string const&而不是string_view

第二个很重要的原因是它允许在没有副本的情况下使用子string。 假设你正在parsing一个2千兆字节的jsonstring(!)²。 如果将其parsing为std::string ,则每个存储节点名称或值的parsing节点都会将原始数据从2 GBstring复制到本地节点。

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

你可以得到的加速只是荒谬的。

这是一个极端的例子,但其他的“得到一个子string并使用它”的情况下,也可以通过string_view产生体面的加速。

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

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

如果std::string_view有一个标志,如果它是空终止(或更奇特),它甚至会删除使用std::string const&最后一个原因。

有一个情况,采取std::string没有const&是最佳的一个std::string_view 。 如果你需要在调用之后无限期地拥有一个string的副本,那么采取价值是有效的。 你要么在SBO的情况下(没有分配,只有几个字符拷贝来复制它),或者你将能够堆分配的缓冲区移动到本地的std::string 。 有两个重载std::string&&std::string_view可能会更快,但只是勉强,这会导致适度的代码膨胀(这可能会花费你所有的速度增益)。


¹小缓冲区优化

²实际使用情况。

它可以做的一件事是避免构造一个std::string对象的情况下从一个NULL结束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. 

主要有两个原因:

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

有一个切片的好处是多重的:

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

更好, 更一致的performance。


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

具体来说,当你有一个std::string const&参数时,不能保证引用string不会被修改。 因此,编译器必须在每次调用后将string的内容重新获取为不透明的方法(指向数据,长度…的指针)。

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

string_view提高性能的一个方法是它可以很容易地删除前缀和后缀。 在这种情况下,string_view只能将前缀大小添加到指向某个string缓冲区的指针,或者从字节计数器中减去后缀大小,这通常很快。 std :: string另一方面必须复制它的字节,当你做一些像substr(这样你得到一个新的string,拥有它的缓冲区,但在许多情况下,你只是想获得原始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"); 

更新:

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

 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 

std::string_view基本上只是一个const char*的包装。 并且传递const char*意味着与传递const string* (或const string& )相比,系统中会less一个指针,因为string*隐含着类似于:

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

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

然而, std::string_viewconst char*之间的一个不同之处在于,string_views不需要以null结尾(它们具有内置大小),并且这允许对较长string进行随机就地拼接。