std::string_view
сделал это в C ++ 17 и широко рекомендуется использовать его вместо const std::string&
,
Одна из причин — производительность.
Может кто-нибудь объяснить, как именно так std::string_view
будет / будет быстрее чем const std::string&
когда используется как тип параметра? (давайте предположим, что в вызываемом номере не сделано ни одной копии)
std::string_view
быстрее в нескольких случаях.
Первый, std::string const&
требует, чтобы данные были в std::string
, а не необработанный массив C, char const*
возвращается C API, std::vector<char>
производится с помощью какого-либо механизма десериализации и т. д. Преобразование избегаемого формата позволяет избежать копирования байтов и (если строка длиннее SBO the для конкретного 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] );
}
В string_view
случай, но будет, если foo
взял, принял std::string const&
вместо string_view
,
Вторая действительно важная причина заключается в том, что она позволяет работать с подстроками без копирования. Предположим, вы анализируете 2-гигабайтную строку json (!) ². Если вы разберете это в std::string
каждый такой узел синтаксического анализа, где они хранят имя или значение узла копии исходные данные из строки 2 ГБ на локальный узел.
Вместо этого, если вы анализируете это std::string_view
s, узлы обращаться к исходным данным. Это может сэкономить миллионы выделенных ресурсов и сократить вдвое требования к памяти при разборе.
Ускорение, которое вы можете получить, просто смешно.
Это крайний случай, но другие случаи «получить подстроку и работать с ней» также могут генерировать приличные ускорения с string_view
,
Важной частью решения является то, что вы теряете, используя std::string_view
, Это не так много, но это что-то.
Вы теряете неявное нулевое завершение, и это все. Таким образом, если одна и та же строка будет передана 3 функциям, каждая из которых требует нулевого терминатора, преобразование в std::string
один раз может быть мудрым. Таким образом, если известно, что вашему коду нужен нулевой терминатор, и вы не ожидаете, что строки будут получены из буферов в стиле C или тому подобного, возможно, возьмите std::string const&
, В противном случае возьмите std::string_view
,
Если std::string_view
имел флаг, который указывал, что, если это было нулевым завершением (или что-то более причудливое), это удалило бы даже эту последнюю причину использовать std::string const&
,
Есть случай, когда std::string
без const&
является оптимальным в течение std::string_view
, Если вам нужно владеть копией строки в течение неопределенного времени после вызова, эффективный захват по значению. Вы будете либо в случае SBO (и без выделения, всего несколько копий символов для его дублирования), или вы сможете переехать выделенный в куче буфер в локальный std::string
, Имея две перегрузки std::string&&
а также std::string_view
может быть быстрее, но только незначительно, и это приведет к небольшому раздуванию кода (что может стоить вам всего прироста скорости).
¹ Оптимизация малого буфера
² Фактический вариант использования.
Один из способов, с помощью которого string_view улучшает производительность, заключается в том, что он позволяет легко удалять префиксы и суффиксы. Под капотом string_view можно просто добавить размер префикса к указателю на некоторый строковый буфер или вычесть размер суффикса из счетчика байтов, обычно это быстро. С другой стороны, std :: string должен копировать свои байты, когда вы делаете что-то вроде substr (таким образом, вы получаете новую строку, которой принадлежит ее буфер, но во многих случаях вы просто хотите получить часть оригинальной строки без копирования). Пример:
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
Есть две основные причины:
string_view
это фрагмент в существующем буфере, он не требует выделения памятиstring_view
передается по значению, а не по ссылкеПреимущества наличия среза множественны:
char const*
или же char[]
без выделения нового буфераЛучше и более последовательный производительность во всем.
Передача по значению также имеет преимущества по сравнению с передачей по ссылке, поскольку имеет псевдонимы.
В частности, когда у вас есть std::string const&
Параметр, нет гарантии, что ссылочная строка не будет изменена. В результате компилятор должен повторно извлекать содержимое строки после каждого вызова в непрозрачный метод (указатель на данные, длину, …).
С другой стороны, при прохождении string_view
по значению, компилятор может статически определить, что никакой другой код не может изменить длину и указатели данных, находящиеся сейчас в стеке (или в регистрах). В результате он может «кэшировать» их через вызовы функций.
Единственное, что он может сделать, это избежать создания 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.
std::string_view
в основном просто обертка вокруг const char*
, И прохождение const char*
означает, что в системе будет меньше указателя по сравнению с передачей const string*
(или же const string&
), так как string*
подразумевает что-то вроде:
string* -> char* -> char[]
| string |
Очевидно, что для передачи константных аргументов первый указатель является излишним.
постскриптум Одна финансовая разница между std::string_view
а также const char*
тем не менее, это то, что string_views не обязательно должны заканчиваться нулем (они имеют встроенный размер), и это допускает случайное соединение на месте более длинных строк.