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


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

одной из причин является производительность.

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

5 154

5 ответов:

std::string_view быстрее в некоторых случаях.

первый, std::string const& необходимо, чтобы данные были в std::string, а не сырой массив C, a char const* возвращается C API, a std::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 завершен (или что-то более причудливое), он удалил бы даже эту последнюю причину использовать a std::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 (они имеют встроенный размер), и это позволяет случайным образом на месте сращивания более длинных строк.