Как именно std :: string_view быстрее, чем const std :: string &?
std::string_view
сделал это на С++ 17, и рекомендуется использовать его вместо const std::string&
.
Одной из причин является производительность.
Может кто-нибудь объяснить, насколько точно std::string_view
будет/будет быстрее, чем const std::string&
при использовании в качестве типа параметра? (пусть не предполагается, что копии не указаны)
Ответы
Ответ 1
std::string_view
быстрее в нескольких случаях.
Во-первых, std::string const&
требует, чтобы данные находились в std::string
, а не как исходный массив C, a char const*
, возвращаемый API C, a std::vector<char>
, созданный некоторым механизмом десериализации, и т.д. форматирование позволяет избежать копирования байтов и (если длина строки больше, чем SBO¹ для конкретной реализации 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
, каждый такой синтаксис node, где они хранят имя или значение node, копирует исходные данные из строки 2 gb в локальный node.
Вместо этого, если вы проанализируете его на 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&
является оптимальным над a std::string_view
. Если вам нужно владеть копией строки неограниченно после вызова, то эффективная по эффективности. Вы либо будете в случае SBO (и не выделяете, а всего несколько копий символов, чтобы дублировать его), или вы сможете переместить буфер, выделенный в кучу, в локальный std::string
. Наличие двух перегрузок std::string&&
и std::string_view
может быть более быстрым, но только незначительно, и это вызовет размытие кода (которое может стоить вам всех приростов скорости).
¹ Оптимизация малого буфера
² Фактический прецедент.
Ответ 2
Один из способов, с помощью которого 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");
Обновление:
Я написал очень простой тест, чтобы добавить некоторые реальные цифры. Я использовал awesome библиотеку тестов 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
Ответ 3
Есть две основные причины:
-
string_view
- это срез в существующем буфере, для него не требуется выделение памяти
-
string_view
передается по значению, а не по ссылке
Преимущества наличия среза несколько:
- вы можете использовать его с
char const*
или char[]
без выделения нового буфера
- вы можете взять несколько срезов и подклассов в существующий буфер без выделения
Подстрока
- - это O (1), а не O (N)
- ...
Лучшая и более стабильная производительность на всем протяжении.
Передача по значению также имеет преимущества перед передачей по ссылке, поскольку псевдонимы.
В частности, если у вас есть параметр std::string const&
, нет гарантии, что эталонная строка не будет изменена. В результате компилятор должен повторно извлекать содержимое строки после каждого вызова в непрозрачный метод (указатель на данные, длину,...).
С другой стороны, при передаче значения string_view
по значению компилятор может статически определять, что ни один другой код не может изменять указатели длины и данных в стеке (или в регистре). В результате он может "кэшировать" их через вызовы функций.
Ответ 4
Единственное, что он может сделать, это не конструировать объект 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.
Ответ 5
std::string_view
- это просто обертка вокруг const char*
. И передача const char*
означает, что в системе будет меньше указателей по сравнению с передачей const string*
(или const string&
), потому что string*
подразумевает что-то вроде:
string* -> char* -> char[]
| string |
Ясно, что для передачи аргументов const первый указатель лишний.
p.s. Тем не менее одно существенное различие между std::string_view
и const char*
заключается в том, что string_views не обязательны для завершения с нулевым значением (они имеют встроенный размер), и это позволяет случайное на месте сплайсинг более длинных строк.