Как именно std:: string view работает быстрее, чем const std::string&?
std::string_view сделал это на C++17 и широко рекомендуется использовать его вместо const std::string&.
одной из причин является производительность.
может кто-нибудь объяснить как ровноstd::string_view Это будет быстрее, чем const std::string& при использовании в качестве типа параметра? (предположим, что никакие копии в вызываемом абоненте не сделаны)
5 ответов:
std::string_viewбыстрее в некоторых случаях.первый,
std::string const&необходимо, чтобы данные были вstd::string, а не сырой массив C, achar const*возвращается C API, astd::vector<char>произведены некоторые десериализации, и т. д. Преобразование избегаемого формата позволяет избежать копирования байтов и (если строка длиннее SBO1 для конкретного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случае, но было бы, если бы наstd::string const&вместоstring_view.вторая действительно большая причина заключается в том, что он позволяет работать с подстроками без копирования. Предположим, вы анализируете строку JSON размером 2 гигабайта (!)2. Если вы разобрать его на
std::string, каждый такой узел разбора, где они хранят имя или значение узла копии исходные данные из строки 2 Гб на локальном узле.вместо этого, если вы разбираете его на
std::string_views, узлы см. в оригинале данные. Это может сэкономить миллионы выделений и сократить вдвое требования к памяти во время синтаксического анализа.ускорение, которое вы можете получить, просто смешно.
это крайний случай, но другие случаи "получить подстроку и работать с ней" также могут генерировать приличные ускорения с
string_view.важной частью решения является то, что вы теряете, используя
std::string_view. Это не так много, но что-то есть.вы теряете неявное нулевое завершение, и это примерно оно. Поэтому, если одна и та же строка будет передана в 3 функции, все из которых требуют нулевого Терминатора, преобразование в
std::stringодин раз может быть мудрым. Таким образом, если ваш код, как известно, нуждается в нулевом Терминаторе, и вы не ожидаете, что строки будут подаваться из буферов c-стиля или тому подобного, возможно, возьмитеstd::string const&. В противном случае возьмитеstd::string_view.если
std::string_viewбыл флаг, который заявил, что если бы он был null завершен (или что-то более причудливое), он удалил бы даже эту последнюю причину использовать astd::string const&.есть случай, когда, взяв
std::stringСconst&является оптимальным по сравнению сstd::string_view. Если вам нужно владеть копией строки на неопределенный срок после вызова, взятие по значению является эффективным. Вы либо будете в случае SBO (и никаких выделений, только несколько копий символов, чтобы дублировать его), либо вы сможете движение выделенный кучей буфер в локальныйstd::string. Имея две перегрузкиstd::string&&иstd::string_viewможет быть быстрее, но незначительно, и это вызовет скромное раздувание кода (что может стоить вам всех выигрышей в скорости).
1 Небольшая Оптимизация Буфера
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");обновление:
я написал очень простой тест, чтобы добавить некоторые реальные цифры. Я использовал удивительный библиотека Google benchmark. Протестированные функции:
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
есть 2 основные причины:
string_viewэто срез в существующем буфере, он не требует выделения памятиstring_viewпередается по значению, а не по ссылке
преимущества наличия ломтика кратны:
- вы можете использовать его с
char const*илиchar[]без выделения нового буфера- вы можете взять несколько срезы и подслизы в существующий буфер без выделения
- подстрока O (1), а не O(N)
- ...
лучше и более последовательны производительность во всем.
передача по значению также имеет преимущества перед передачей по ссылке, потому что сглаживание.
в частности, когда у вас есть
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 |очевидно, что для передачи аргументов const первый указатель является излишним.
п.з. одна существенная разница между
std::string_viewиconst char*, тем не менее, это то, что string_views не являются требуется, чтобы быть null-terminated (они имеют встроенный размер), и это позволяет случайным образом на месте сращивания более длинных строк.