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