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_view
和const char*
之间的一个不同之处在于,string_views不需要以null结尾(它们具有内置大小),并且这允许对较长string进行随机就地拼接。