std::string_view
已经使用了C ++ 17,并且广泛建议使用它代替const std::string&
。
其中一个原因是表现。
有人可以解释当用作参数类型时完全 std::string_view
/会比const std::string&
更快吗? (让我们假设被叫方没有副本)
答案 0 :(得分:175)
std::string_view
在某些情况下更快。
首先,std::string const&
要求数据位于std::string
,而不是原始C数组,C API返回的char const*
,由std::vector<char>
生成的std::string
一些反序列化引擎等。避免格式转换避免了复制字节,并且(如果字符串比特定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] );
}
实现的SBO¹长)避免了内存分配。
string_view
foo
案例中没有分配,但如果std::string const&
使用string_view
代替std::string
,则会有分配。
第二个非常重要的原因是它允许在没有副本的情况下使用子字符串。假设你正在解析一个2千兆字节的json字符串(!)²。如果将其解析为std::string_view
,则每个此类解析节点存储节点的名称或值将原始数据从2 gb字符串复制到本地节点。
相反,如果您将其解析为string_view
,则节点将引用到原始数据。这可以在解析过程中节省数百万个分配并减少内存需求。
你可以获得的加速非常荒谬。
这是一个极端的情况,但其他“获取子字符串并使用它”的情况也可以使用std::string_view
生成适当的加速。
决定的一个重要部分是使用std::string
丢失的内容。它并不多,但它确实存在。
你失去了隐式的空终止,这就是它。因此,如果将相同的字符串传递给3个函数,所有这些函数都需要空终止符,则转换为std::string const&
一次可能是明智的。因此,如果您的代码已知需要一个空终止符,并且您不希望从C风格的源缓冲区等提供字符串,则可能需要std::string_view
。否则请std::string_view
。
如果std::string const&
有一个标志,表明它是否为空终止(或更高级的东西),它甚至会删除使用std::string
的最后一个原因。
有一种情况是,const&
没有std::string_view
的情况优于std::string
。如果您需要在通话后无限期拥有该字符串的副本,则取值按钮是有效的。您将处于SBO案例中(并且没有分配,只需要几个字符副本来复制它),或者您将能够将堆分配的缓冲区移动到本地{{1 }}。有两个重载std::string&&
和std::string_view
可能会更快,但只是稍微有点,这会导致适度的代码膨胀(这可能会让你失去所有的速度)。
¹小缓冲区优化
²实际使用案例。
答案 1 :(得分:49)
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 benchmark library。基准功能是:
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,&#34; -O3 -DNDEBUG
&#34;):
Benchmark Time CPU Iterations
-------------------------------------------------------------------
BM_remove_prefix_string 90 ns 90 ns 7740626
BM_remove_prefix_string_view 6 ns 6 ns 120468514
答案 2 :(得分:42)
主要有两个原因:
string_view
是现有缓冲区中的一个片段,它不需要内存分配string_view
按值传递,而非按参考传递切片的优点是多重的:
char const*
或char[]
一起使用,而无需分配新的缓冲区更好,更加一致的表现。
传递值也比传递引用更有优势,因为别名。
具体来说,如果您有std::string const&
参数,则无法保证不会修改引用字符串。因此,编译器必须在每次调用后重新获取字符串的内容为opaque方法(指向数据,长度,...)。
另一方面,当传递string_view
的值时,编译器可以静态地确定没有其他代码可以修改堆栈(或寄存器)中现在的长度和数据指针。结果,它可以&#34;缓存&#34;他们跨越函数调用。
答案 3 :(得分: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.
答案 4 :(得分:5)
std::string_view
基本上只是const char*
的包装。并且传递const char*
意味着与传递const string*
(或const string&
)相比,系统中将少一个指针,因为string*
意味着:
string* -> char* -> char[]
| string |
显然,为了传递const参数,第一个指针是多余的。
ps std::string_view
和const char*
之间的一个重要差异是,string_views不需要以空值终止(它们具有内置大小),这允许随机拼接更长的字符串。