Std :: string_view tam olarak nasıl const std :: string & 'den daha hızlıdır?


221

std::string_viewC ++ 17'ye getirdi ve bunun yerine yaygın olarak kullanılması önerilir const std::string&.

Sebeplerden biri performanstır.

Birisi parametre tipi olarak kullanıldığından tam olarak ne kadar std::string_viewhızlı / hızlı olacağını açıklayabilir const std::string&mi? (diyelim ki callee'de hiçbir kopya yapılmadı)


7
std::string_view(char * begin, char * end) çiftinin bir soyutlamasıdır. Bunu yaparken std::stringgereksiz bir kopya olur.
QuestionC

Bence soru tam olarak hangisinin daha hızlı olduğu değil, ne zaman kullanılacağıdır. Dize üzerinde biraz manipülasyona ihtiyacım varsa ve kalıcı değilse ve / veya orijinal değerini koruduysam, string_view mükemmeldir, çünkü dizenin bir kopyasını yapmam gerekmez. Ama sadece string :: find kullanarak dize bir şey kontrol etmek gerekirse, o zaman başvuru daha iyidir.
TheArquitect

İstemediğiniz zaman bunu kullanmak @QuestionC API için kısıtlamak için std::string(ham diziler, vektörler, kabul edebilir string_view std::basic_string<>varsayılan olmayan allocators vs vs vs Oh ve diğer string_views besbelli birlikte)
sehe

Yanıtlar:


213

std::string_view birkaç durumda daha hızlıdır.

İlk olarak, std::string const&bir olmak veri gerektirir std::string, ve bir ham C dizi, bir char const*C API tarafından döndürülen bir std::vector<char>bazı seri kaldırma kaçınılması format dönüştürme önler bayt kopyalama motoru, vs. ve üretilen (dize daha uzun olması durumunda SBO¹ özel std::stringuygulama için) bellek tahsisini önler.

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_viewDavada hiçbir tahsis yapılmaz , ancak fooa std::string const&yerine alınıyor string_view.

İkinci büyük neden, kopya olmadan alt dizelerle çalışmaya izin vermesidir. 2 gigabaytlık bir json dizesi (!) ² ayrıştırdığınızı varsayalım. İçine std::stringayrıştırırsanız, düğümün adını veya değerini sakladıkları her ayrıştırma düğümü , orijinal verileri 2 gb dizesinden yerel bir düğüme kopyalar .

Bunun yerine, bunları std::string_views olarak ayrıştırırsanız , düğümler orijinal verilere başvurur . Bu, ayrıştırma sırasında milyonlarca ayırmayı kaydedebilir ve bellek gereksinimlerini yarıya indirebilir.

Alabileceğiniz hızlanma saçma.

Bu aşırı bir durumdur, ancak diğer "bir alt dize alın ve onunla çalışın" durumları da iyi hızlanmalar üretebilir string_view.

Kararın önemli bir kısmı, ne kullanarak kaybettiğinizdir std::string_view. Çok değil, ama bir şey.

Örtük null sonlandırmayı kaybedersiniz ve bu da bununla ilgilidir. Dolayısıyla, aynı dize, tümü bir boş sonlandırıcı gerektiren 3 işleve geçirilecekse, bir std::stringkez dönüştürmek akıllıca olabilir. Bu nedenle, kodunuzun boş bir sonlandırıcıya ihtiyacı olduğu biliniyorsa ve C stili kaynaklı arabelleklerden veya benzerlerinden beslenen dizelerden beklemiyorsanız, belki de a std::string const&. Aksi takdirde a std::string_view.

Eğer std::string_viewnull sonlandırıldığını (ya da bir şey daha meraklısı) belirten bir işarete sahip olsaydı , a kullanmak için son nedenden bile çıkarırdı std::string const&.

A std::stringile hayır almanın a yerine const&en uygun olduğu bir durum vardır std::string_view. Aramadan sonra dizenin bir kopyasına sahip olmanız gerekiyorsa, by-value almak etkilidir. SBO durumunda olacaksınız (ve ayırma yok, çoğaltmak için sadece birkaç karakter kopyası) veya yığınla ayrılan arabelleği yerel bir konuma taşıyabilirsinizstd::string . İki aşırı yüke sahip olmak std::string&&ve std::string_viewdaha hızlı olabilir, ancak sadece marjinal olarak ve mütevazı kod şişmesine neden olur (bu da tüm hız kazançlarına mal olabilir).


¹ Küçük Tampon Optimizasyonu

² Gerçek kullanım durumu.


8
Ayrıca mülkiyeti de kaybedersiniz. Bu, yalnızca dize döndürüldüğünde ilgi çekicidir ve yeterince uzun süre hayatta kalması garanti edilen bir tamponun alt dizesinin yanı sıra herhangi bir şey olması gerekebilir . Aslında, sahiplik kaybı çok iki ucu keskin bir silahtır.
Tekilleştirici

SBO kulağa tuhaf geliyor. Her zaman SSO (küçük dize optimizasyonu) duydum
phuclv

@phu Elbette; ancak ipleri kullandığınız tek şey dizeler değildir.
Yakk - Adam Nevraumont

@phuclv SSO sadece küçük bir tampon optimizasyonu anlamına gelen özel bir SBO vakasıdır . Alternatif terimler küçük veri tercihidir. , küçük nesne op. veya küçük boyutlu opt. .
Daniel Langr

59

String_view'in performansı geliştirmesinin bir yolu, öneklerin ve soneklerin kolayca kaldırılmasına izin vermesidir. Kaputun altında, string_view, bazı dize arabelleğine bir işaretçiye önek boyutunu ekleyebilir veya sonek boyutunu bayt sayacından çıkarabilir, bu genellikle hızlıdır. Öte yandan std :: string, substr gibi bir şey yaptığınızda baytlarını kopyalamak zorundadır (bu şekilde tamponuna sahip yeni bir dize alırsınız, ancak çoğu durumda orijinal dizinin bir kısmını kopyalamaksızın almak istersiniz). Misal:

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

Std :: string_view ile:

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

Güncelleme:

Bazı gerçek sayılar eklemek için çok basit bir kıyaslama yazdım. Harika google benchmark kütüphanesi kullandım . Karşılaştırma işlevleri:

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

Sonuçlar

(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
Gerçek bir karşılaştırma ölçütü sağlamanız harika. Bu gerçekten ilgili kullanım durumlarında neler kazanılabileceğini göstermektedir.
Daniel Kamil Kozar

1
@DanielKamilKozar Geri bildiriminiz için teşekkür ederiz. Ben de kriterlerin değerli olduğunu düşünüyorum, bazen her şeyi değiştiriyorlar.
Pavel Davydov

47

2 ana neden vardır:

  • string_view mevcut bir arabellekteki bir dilimdir, bellek ayırma gerektirmez
  • string_view referans ile değil, değere göre geçirilir

Bir dilime sahip olmanın avantajları çoktur:

  • yeni bir arabellek ayırmadan char const*veya char[]ayırmadan kullanabilirsiniz
  • ayırmadan varolan bir arabelleğe birden çok dilim ve alt dil alabilirsiniz
  • alt dize O (1), O (N) değil
  • ...

Her yerde daha iyi ve daha tutarlı performans.


Değere göre geçmenin, referansla geçmeye göre de avantajları vardır, çünkü takma adlandırma.

Özellikle, bir std::string const&parametreniz olduğunda, referans dizesinin değiştirilmeyeceğine dair bir garanti yoktur. Sonuç olarak, derleyici her çağrıdan sonra dizenin içeriğini opak bir yönteme (veri işaretçisi, uzunluk, ...) yeniden getirmelidir.

Öte yandan, bir string_viewby by değerini iletirken, derleyici statik olarak başka hiçbir kodun artık yığın üzerinde (veya yazmaçlarda) uzunluk ve veri işaretleyicilerini değiştiremeyeceğini belirleyebilir. Sonuç olarak, bunları işlev çağrıları arasında "önbelleğe alabilir".


36

Yapabileceği bir şey, std::stringboş bir sonlandırılmış dizeden örtük bir dönüştürme durumunda bir nesne oluşturmaktan kaçınmaktır :

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.

12
O söyleyerek değer olabilir const std::string str{"goodbye!"}; foo(str);muhtemelen olmayacak dize & kıyasla daha hızlı string_view birlikte olmak
Martin Bonner Monica destekler

1
string_viewBir işaretçinin aksine iki işaretçiyi kopyalamak zorunda olduğu için yavaş olmayacak const string&mı?
balki

9

std::string_viewtemelde a const char*. Geçme const char*, sistemde geçme const string*(veya const string&) ile karşılaştırıldığında daha az bir işaretçi olacağı anlamına gelir , çünkü şöyle bir string*şey ifade eder:

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

Açıkça const argümanlarını iletmek amacıyla ilk işaretçi gereksizdir.

psstd::string_view Ve const char*yine de, arasındaki önemli farklardan biri , string_view öğelerinin boş sonlandırılması gerekmediği (yerleşik boyutta) ve bu da daha uzun dizelerin rastgele yerinde birleştirilmesine izin vermesidir.


4
Downvotes nedir? std::string_viewsadece fantezi const char*, dönem. GCC bunları şu şekilde uygular:class basic_string_view {const _CharT* _M_str; size_t _M_len;}
n.caillou

4
sadece 65K temsilcisi olsun (şu anki 65) ve bu kabul edilen cevap (kargo kült kalabalığa dalgalar) :)
mlvljr

7
@mlvljr Kimse geçmiyor std::string const*. Ve bu diyagram anlaşılmaz. @ n.caillou: Kendi yorumunuz cevaptan daha doğru. Bu string_view"fantezi char const*" den daha fazlasını yapar - gerçekten çok açık.
17'de se

@sehe ben kimse olabilir, hiçbir problemo (yani bir const dize bir işaretçi (veya başvuru) geçen, neden olmasın?) :)
mlvljr

2
@sehe Bir optimizasyon veya yürütme açısından anlamak, std::string const*ve std::string const&sen, aynı değil mi?
n.caillou
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.