Bagaimana tepatnya std :: string_view lebih cepat daripada const std :: string &?

221

std::string_viewtelah berhasil mencapai C ++ 17 dan direkomendasikan untuk menggunakannya sebagai gantinya const std::string&.

Salah satu alasannya adalah kinerja.

Dapatkah seseorang menjelaskan bagaimana tepatnya std::string_view / akan lebih cepat daripada const std::string&ketika digunakan sebagai tipe parameter? (Mari kita asumsikan tidak ada salinan di callee yang dibuat)

Patryk
sumber
7
std::string_viewhanyalah abstraksi dari pasangan (char * begin, char * end). Anda menggunakannya saat membuat std::stringsalinan yang tidak perlu.
QuestionC
Menurut pendapat saya pertanyaannya bukan yang mana yang lebih cepat, tetapi kapan menggunakannya. Jika saya memerlukan manipulasi pada string dan itu tidak permanen dan / atau mempertahankan nilai aslinya, string_view sempurna karena saya tidak perlu membuat salinan string untuk itu. Tetapi jika saya hanya perlu memeriksa sesuatu pada string menggunakan string :: find misalnya, maka referensi lebih baik.
TheArquitect
@QuestionC Anda menggunakannya ketika Anda tidak ingin API Anda dibatasi std::string(string_view dapat menerima array mentah, vektor, std::basic_string<>dengan pengalokasi non-default dll. Dll. Oh, dan string_views lainnya jelas)
lihat

Jawaban:

213

std::string_view lebih cepat dalam beberapa kasus.

Pertama, std::string const&membutuhkan data dalam a std::string, dan bukan array C mentah, char const*dikembalikan oleh API C, yang std::vector<char>dihasilkan oleh beberapa mesin deserialisasi, dll. Konversi format yang dihindari menghindari menyalin byte, dan (jika string lebih panjang dari SBO¹ untuk std::stringimplementasi tertentu ) menghindari alokasi memori.

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] );
}

Tidak ada alokasi yang dilakukan dalam string_viewkasus ini, tetapi akan ada jika foomengambil std::string const&alih a string_view.

Alasan kedua yang sangat besar adalah bahwa ia memungkinkan bekerja dengan substring tanpa salinan. Misalkan Anda mengurai string 2 gigabyte json (!) ². Jika Anda menguraikannya std::string, masing-masing node parse di mana mereka menyimpan nama atau nilai dari sebuah node menyalin data asli dari string 2 gb ke node lokal.

Sebagai gantinya, jika Anda menguraikannya ke std::string_views, node merujuk ke data asli. Ini dapat menghemat jutaan alokasi dan membagi dua persyaratan memori selama parsing.

Speedup yang bisa Anda dapatkan cukup konyol.

Ini adalah kasus yang ekstrim, tetapi kasus "dapatkan substring dan bekerja dengannya" yang lain juga dapat menghasilkan speedup yang layak string_view.

Bagian penting dari keputusan adalah apa yang Anda hilangkan dengan menggunakan std::string_view. Tidak banyak, tapi itu sesuatu.

Anda kehilangan terminasi nol implisit, dan hanya itu saja. Jadi jika string yang sama akan diteruskan ke 3 fungsi yang semuanya membutuhkan terminator nol, konversi ke std::stringsekali mungkin bijaksana. Jadi, jika kode Anda diketahui membutuhkan terminator nol, dan Anda tidak mengharapkan string diumpankan dari buffer bersumber gaya C atau sejenisnya, mungkin ambil a std::string const&. Kalau tidak ambil a std::string_view.

Jika std::string_viewmemiliki bendera yang menyatakan apakah itu nol diakhiri (atau sesuatu yang lebih menarik) itu akan menghapus bahkan alasan terakhir untuk menggunakan a std::string const&.

Ada kasus di mana mengambil std::stringdengan tidak const&optimal lebih dari a std::string_view. Jika Anda perlu memiliki salinan string tanpa batas waktu setelah panggilan, mengambil menurut nilai lebih efisien. Anda akan berada dalam case SBO (dan tidak ada alokasi, hanya beberapa salinan karakter untuk menduplikasinya), atau Anda akan dapat memindahkan buffer yang dialokasikan heap ke lokal std::string. Memiliki dua kelebihan std::string&&dan std::string_viewmungkin lebih cepat, tetapi hanya sedikit, dan itu akan menyebabkan kode gembung sederhana (yang dapat dikenakan biaya semua kenaikan kecepatan).


¹ Optimasi Penyangga Kecil

² Kasus penggunaan aktual.

Yakk - Adam Nevraumont
sumber
8
Anda juga kehilangan kepemilikan. Yang hanya menarik jika string dikembalikan dan mungkin harus apa pun selain sub-string buffer yang dijamin dapat bertahan cukup lama. Sebenarnya, kehilangan kepemilikan adalah senjata yang sangat bermata dua.
Deduplikator
SBO terdengar aneh. Saya selalu mendengar SSO (optimasi string kecil)
phuclv
@ phu Tentu; tetapi string bukan satu-satunya hal yang Anda gunakan untuk trik.
Yakk - Adam Nevraumont
@ phuclv SSO hanyalah kasus khusus SBO, yang merupakan singkatan dari optimasi buffer kecil . Istilah alternatif adalah data kecil memilih. , memilih objek kecil. , atau pilihan ukuran kecil. .
Daniel Langr
59

Salah satu cara string_view meningkatkan kinerja adalah bahwa ia memungkinkan untuk menghapus awalan dan akhiran dengan mudah. Di bawah tenda, string_view hanya dapat menambahkan ukuran awalan ke pointer ke beberapa buffer string, atau kurangi ukuran akhiran dari penghitung byte, ini biasanya cepat. std :: string di sisi lain harus menyalin byte-nya ketika Anda melakukan sesuatu seperti substr (cara ini Anda mendapatkan string baru yang memiliki buffer, tetapi dalam banyak kasus Anda hanya ingin mendapatkan bagian dari string asli tanpa menyalin). Contoh:

std::string str{"foobar"};
auto bar = str.substr(3);
assert(bar == "bar");

Dengan std :: string_view:

std::string str{"foobar"};
std::string_view bar{str.c_str(), str.size()};
bar.remove_prefix(3);
assert(bar == "bar");

Memperbarui:

Saya menulis patokan yang sangat sederhana untuk menambahkan beberapa bilangan real. Saya menggunakan pustaka tolok ukur google yang mengagumkan . Fungsi yang dibandingan adalah:

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

Hasil

(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
Pavel Davydov
sumber
2
Sangat bagus bahwa Anda memberikan tolok ukur yang sebenarnya. Ini benar-benar menunjukkan apa yang bisa diperoleh dalam kasus penggunaan yang relevan.
Daniel Kamil Kozar
1
@DanielKamilKozar Terima kasih atas umpan baliknya. Saya juga berpikir tolok ukur itu berharga, terkadang mereka mengubah segalanya.
Pavel Davydov
47

Ada 2 alasan utama:

  • string_view adalah irisan dalam buffer yang ada, tidak memerlukan alokasi memori
  • string_view dilewatkan oleh nilai, bukan oleh referensi

Keuntungan memiliki sepotong adalah beberapa:

  • Anda dapat menggunakannya dengan char const*atau char[]tanpa mengalokasikan buffer baru
  • Anda dapat mengambil beberapa irisan dan berlangganan ke buffer yang ada tanpa mengalokasikan
  • substring adalah O (1), bukan O (N)
  • ...

Kinerja yang lebih baik dan lebih konsisten di seluruh.


Melewati dengan nilai juga memiliki kelebihan dibandingkan melewatinya dengan referensi, karena aliasing.

Khususnya, ketika Anda memiliki std::string const&parameter, tidak ada jaminan bahwa string referensi tidak akan dimodifikasi. Akibatnya, kompiler harus mengambil kembali konten string setelah setiap panggilan menjadi metode buram (pointer ke data, panjang, ...).

Di sisi lain, ketika melewati string_viewnilai, kompiler secara statis dapat menentukan bahwa tidak ada kode lain yang dapat memodifikasi panjang dan data pointer sekarang di stack (atau dalam register). Sebagai hasilnya, itu bisa "cache" mereka di panggilan fungsi.

Matthieu M.
sumber
36

Satu hal yang dapat dilakukan adalah menghindari membangun std::stringobjek jika ada konversi implisit dari string yang diakhiri dengan nol:

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.
juanchopanza
sumber
12
Mungkin patut dikatakan bahwa const std::string str{"goodbye!"}; foo(str);mungkin tidak akan ada yang lebih cepat dengan string_view daripada dengan string &
Martin Bonner mendukung Monica
1
Tidak string_viewakan lambat karena harus menyalin dua pointer sebagai lawan satu pointer const string&?
balki
9

std::string_viewpada dasarnya hanyalah sebuah pembungkus di sekitar a const char*. Dan passing const char*berarti bahwa akan ada satu pointer kurang dalam sistem dibandingkan dengan passing const string*(atau const string&), karena string*menyiratkan sesuatu seperti:

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

Jelas untuk tujuan melewati argumen const pointer pertama tidak berguna.

ps Satu perbedaan mendasar antara std::string_viewdan const char*, bagaimanapun, adalah bahwa string_view tidak diharuskan untuk diakhiri nol (mereka memiliki ukuran bawaan), dan ini memungkinkan penyambungan acak di tempat dari string yang lebih panjang.

n.caillou
sumber
4
Ada apa dengan downvotes? std::string_viewIni hanya mewah const char*, titik. GCC mengimplementasikannya seperti ini:class basic_string_view {const _CharT* _M_str; size_t _M_len;}
n.caillou
4
hanya sampai 65K rep (dari 65 Anda saat ini) dan ini akan menjadi jawaban yang diterima (gelombang ke kerumunan kultus kargo) :)
mlvljr
7
@mlvljr Tidak ada yang lewat std::string const*. Dan diagram itu tidak bisa dipahami. @ n.caillou: Komentar Anda sendiri sudah lebih akurat daripada jawabannya. Itu membuat string_viewlebih dari "mewah char const*" - itu sangat jelas.
lihat
@sehe aku bisa siapa-siapa, tidak ada problemo (yaitu melewati pointer (atau referensi) ke string const, kenapa tidak?) :)
mlvljr
2
@sehe Anda benar-benar memahami itu dari perspektif optimasi atau eksekusi, std::string const*dan std::string const&sama, bukan?
n.caillou