Как именно std :: string_view быстрее, чем const std :: string & amp ;?

std::string_view сделал это в C ++ 17 и широко рекомендуется использовать его вместо const std::string&,

Одна из причин — производительность.

Может кто-нибудь объяснить, как именно так std::string_view будет / будет быстрее чем const std::string& когда используется как тип параметра? (давайте предположим, что в вызываемом номере не сделано ни одной копии)

165

Решение

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_views, узлы обращаться к исходным данным. Это может сэкономить миллионы выделенных ресурсов и сократить вдвое требования к памяти при разборе.

Ускорение, которое вы можете получить, просто смешно.

Это крайний случай, но другие случаи «получить подстроку и работать с ней» также могут генерировать приличные ускорения с 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 может быть быстрее, но только незначительно, и это приведет к небольшому раздуванию кода (что может стоить вам всего прироста скорости).


¹ Оптимизация малого буфера

² Фактический вариант использования.

166

Другие решения

Один из способов, с помощью которого 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
45

Есть две основные причины:

  • string_view это фрагмент в существующем буфере, он не требует выделения памяти
  • string_view передается по значению, а не по ссылке

Преимущества наличия среза множественны:

  • Вы можете использовать его с char const* или же char[] без выделения нового буфера
  • ты можешь взять множественный срезы и сублименты в существующий буфер без выделения
  • подстрока O (1), а не O (N)

Лучше и более последовательный производительность во всем.


Передача по значению также имеет преимущества по сравнению с передачей по ссылке, поскольку имеет псевдонимы.

В частности, когда у вас есть std::string const& Параметр, нет гарантии, что ссылочная строка не будет изменена. В результате компилятор должен повторно извлекать содержимое строки после каждого вызова в непрозрачный метод (указатель на данные, длину, …).

С другой стороны, при прохождении string_view по значению, компилятор может статически определить, что никакой другой код не может изменить длину и указатели данных, находящиеся сейчас в стеке (или в регистрах). В результате он может «кэшировать» их через вызовы функций.

39

Единственное, что он может сделать, это избежать создания 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.
36

std::string_view в основном просто обертка вокруг const char*, И прохождение const char* означает, что в системе будет меньше указателя по сравнению с передачей const string* (или же const string&), так как string* подразумевает что-то вроде:

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

Очевидно, что для передачи константных аргументов первый указатель является излишним.

постскриптум Одна финансовая разница между std::string_view а также const char*тем не менее, это то, что string_views не обязательно должны заканчиваться нулем (они имеют встроенный размер), и это допускает случайное соединение на месте более длинных строк.

5
По вопросам рекламы [email protected]