std :: string_view เร็วกว่า const std :: string & อย่างไร


221

std::string_viewได้ทำให้มันไปที่ C ++ 17 const std::string&และจะมีการใช้กันอย่างแพร่หลายในการใช้มันแทน

เหตุผลหนึ่งคือประสิทธิภาพ

ใครสามารถอธิบายได้ว่า std::string_view / จะเร็วกว่าconst std::string&เมื่อใช้เป็นชนิดพารามิเตอร์อย่างไร (สมมติว่าไม่มีสำเนาใน callee)


7
std::string_viewเป็นเพียงสิ่งที่เป็นนามธรรมของคู่ (char * start, char * end) คุณใช้เมื่อทำการ a std::stringจะเป็นสำเนาที่ไม่จำเป็น
QuestionC

ในความคิดของฉันคำถามไม่ได้เป็นที่หนึ่งที่เร็วขึ้น แต่เมื่อจะใช้พวกเขา ถ้าฉันต้องการการจัดการกับสตริงและไม่ถาวรและ / หรือเก็บค่าเดิม string_view นั้นสมบูรณ์แบบเพราะฉันไม่จำเป็นต้องทำสำเนาสตริง แต่ถ้าฉันต้องการตรวจสอบบางสิ่งบางอย่างในสตริงโดยใช้สตริง :: ค้นหาตัวอย่างแล้วการอ้างอิงจะดีกว่า
TheArquitect

@QuestionC คุณใช้งานเมื่อคุณไม่ต้องการให้ API จำกัดstd::string(string_view สามารถยอมรับอาร์เรย์ดิบ, เวกเตอร์, std::basic_string<>ด้วยตัวจัดสรรที่ไม่ใช่ค่าเริ่มต้น ฯลฯ ฯลฯ ฯลฯ โอ้และเห็นได้ชัดว่า string_views อื่น ๆ )
sehe

คำตอบ:


213

std::string_view เร็วขึ้นในบางกรณี

ก่อนอื่นstd::string const&ต้องการข้อมูลที่อยู่ในstd::stringและไม่ใช่อาร์เรย์ C แบบดิบ, char const*ส่งคืนโดย C API, std::vector<char>สร้างโดยเอ็นจิ้น deserialization, ฯลฯ การแปลงรูปแบบหลีกเลี่ยงหลีกเลี่ยงการคัดลอกไบต์และ (ถ้าสตริงยาวกว่า SBO¹สำหรับ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กรณี แต่จะมีถ้าfooเอาแทนstd::string const&string_view

เหตุผลใหญ่ที่สองคือมันอนุญาตให้ทำงานกับ substrings โดยไม่มีการคัดลอก สมมติว่าคุณกำลังวิเคราะห์สตริง json 2 กิกะไบต์ (!) ² หากคุณแยกวิเคราะห์มันstd::stringแต่ละโหนดแยกวิเคราะห์ดังกล่าวซึ่งพวกเขาเก็บชื่อหรือค่าของโหนดคัดลอกข้อมูลต้นฉบับจากสตริง 2 gb ไปยังโหนดท้องถิ่น

หากคุณแยกวิเคราะห์เป็นstd::string_views โหนดจะอ้างถึงข้อมูลดั้งเดิม สิ่งนี้สามารถบันทึกการจัดสรรได้หลายล้านครั้งและลดความต้องการหน่วยความจำลงครึ่งหนึ่งในระหว่างการแยกวิเคราะห์

การเร่งความเร็วที่คุณจะได้รับนั้นไร้สาระ

นี้เป็นกรณีที่รุนแรง แต่คนอื่น "ได้รับการย่อยและการทำงานกับมัน" กรณียังสามารถสร้าง speedups string_viewที่เหมาะสมกับ

std::string_viewเป็นส่วนสำคัญในการตัดสินใจคือสิ่งที่คุณจะสูญเสียโดยใช้ มันไม่มาก แต่มันเป็นอะไรบางอย่าง

คุณสูญเสียการบอกเลิก null โดยปริยายและนั่นก็เกี่ยวกับมัน ดังนั้นหากสตริงเดียวกันจะถูกส่งผ่านไปยัง 3 ฟังก์ชั่นซึ่งทั้งหมดนี้ต้องการตัวปิดเทอร์มินัล null การแปลงเป็นstd::stringครั้งเดียวอาจฉลาด ดังนั้นหากรหัสของคุณเป็นที่รู้จักกันว่าต้องมีตัวยกเลิกเทอร์มินัลเป็นโมฆะและคุณไม่คาดหวังว่าสตริงที่ป้อนจากบัฟเฟอร์ C-style ที่มาหรืออาจจะใช้std::string const&กัน มิฉะนั้นจะใช้เวลาstd::string_viewมิฉะนั้นจะใช้

หากstd::string_viewมีการตั้งค่าสถานะที่ระบุว่าเป็นโมฆะ (หรือสิ่งที่นักเล่น) มันจะลบแม้เหตุผลสุดท้ายที่จะใช้std::string const&มันจะเอาแม้กระทั่งว่าเหตุผลสุดท้ายที่จะใช้

มีกรณีที่การเป็นstd::stringที่ไม่มีที่เหมาะสมกว่าconst& std::string_viewหากคุณต้องการเป็นเจ้าของสำเนาของสตริงอย่างไม่มีกำหนดหลังจากการโทรการรับค่าจะมีประสิทธิภาพ คุณจะอยู่ในกรณี SBO (และไม่มีการจัดสรรเพียงไม่กี่สำเนาตัวอักษรเพื่อทำซ้ำ) หรือคุณสามารถย้ายบัฟเฟอร์ที่จัดสรรฮีปไปไว้ในstd::stringเครื่อง มีสองเกินพิกัดstd::string&&และstd::string_viewอาจเร็วกว่า แต่มีเพียงเล็กน้อยเท่านั้นและอาจทำให้โค้ดบวมเล็กน้อย (ซึ่งอาจทำให้คุณได้รับความเร็วทั้งหมด)


Optimization การเพิ่มประสิทธิภาพบัฟเฟอร์ขนาดเล็ก

²กรณีการใช้งานจริง


8
คุณสูญเสียความเป็นเจ้าของด้วย ซึ่งน่าสนใจก็ต่อเมื่อสตริงถูกส่งคืนและอาจต้องมีอะไรนอกเหนือจากสตริงย่อยของบัฟเฟอร์ซึ่งรับประกันว่าจะอยู่รอดได้นานพอ ที่จริงแล้วการสูญเสียความเป็นเจ้าของเป็นอาวุธที่มีขอบสองด้านมาก
Deduplicator

SBO ฟังดูแปลก ๆ ฉันมักจะได้ยิน SSO (การเพิ่มประสิทธิภาพสตริงขนาดเล็ก)
27432

@phu Sure; แต่สตริงไม่ใช่สิ่งเดียวที่คุณใช้เคล็ดลับ
Yakk - Adam Nevraumont

@phuclv SSO เป็นเพียงกรณีเฉพาะของ SBO ซึ่งย่อมาจากการเพิ่มประสิทธิภาพบัฟเฟอร์ขนาดเล็ก ข้อกำหนดทางเลือกคือการเลือกใช้ข้อมูลขนาดเล็ก , การเลือกวัตถุขนาดเล็ก หรือเลือกขนาดเล็ก .
Daniel Langr

59

วิธีหนึ่งที่ 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 ฟังก์ชั่น Benchmarked คือ:

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
เป็นเรื่องที่ดีมากที่คุณได้ให้เกณฑ์มาตรฐานจริง นี่แสดงให้เห็นถึงสิ่งที่สามารถได้รับในกรณีการใช้งานที่เกี่ยวข้อง
Daniel Kamil Kozar

1
@DanielKamilKozar ขอบคุณสำหรับคำติชม ฉันยังคิดว่าการเปรียบเทียบมีคุณค่าบางครั้งพวกเขาเปลี่ยนทุกอย่าง
Pavel Davydov

47

มี 2 ​​เหตุผลหลัก:

  • string_view เป็นส่วนหนึ่งในบัฟเฟอร์ที่มีอยู่มันไม่จำเป็นต้องมีการจัดสรรหน่วยความจำ
  • string_view ถูกส่งผ่านตามค่าไม่ใช่ตามการอ้างอิง

ข้อดีของการมีส่วนแบ่งเป็นหลาย:

  • คุณสามารถใช้กับchar const*หรือchar[]ไม่มีการจัดสรรบัฟเฟอร์ใหม่
  • คุณสามารถใช้หลายชิ้นและชุดย่อยลงในบัฟเฟอร์ที่มีอยู่โดยไม่ต้องจัดสรร
  • ซับสตริงคือ O (1) ไม่ใช่ O (N)
  • ...

ประสิทธิภาพที่ดีขึ้นและสอดคล้องกันมากขึ้นทั่ว


การส่งผ่านค่ายังมีข้อดีมากกว่าการผ่านโดยการอ้างอิงเนื่องจาก aliasing

โดยเฉพาะเมื่อคุณมี std::string const&พารามิเตอร์จะไม่มีการรับประกันว่าสตริงการอ้างอิงจะไม่ถูกแก้ไข เป็นผลให้คอมไพเลอร์ต้องดึงเนื้อหาของสตริงอีกครั้งหลังจากการโทรแต่ละครั้งเป็นวิธีทึบแสง (ตัวชี้ไปยังข้อมูลความยาว ... )

ในทางกลับกันเมื่อผ่านstring_viewค่าโดยคอมไพเลอร์สามารถกำหนดแบบคงที่ว่าไม่มีรหัสอื่นสามารถปรับเปลี่ยนความยาวและพอยน์เตอร์ข้อมูลในสแต็ก (หรือในการลงทะเบียน) เป็นผลให้มันสามารถ "แคช" พวกเขาในการเรียกใช้ฟังก์ชั่น


36

สิ่งหนึ่งที่สามารถทำได้คือหลีกเลี่ยงการสร้างstd::stringวัตถุในกรณีของการแปลงโดยนัยจากสตริงที่สิ้นสุดด้วยค่า null:

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
มันอาจจะคุ้มค่าที่จะบอกว่าconst std::string str{"goodbye!"}; foo(str);อาจจะไม่เร็วกว่านี้ด้วย string_view มากกว่า string &
Martin Bonner สนับสนุน Monica

1
เคยชินstring_viewจะช้าตามที่มีการคัดลอกสองตัวชี้เมื่อเทียบกับหนึ่งในตัวชี้const string&?
balki

9

std::string_viewconst char*เป็นเพียงห่อหุ้มรอบที่ และการส่งผ่านconst char*หมายความว่าจะมีตัวชี้น้อยกว่าหนึ่งตัวในระบบเมื่อเปรียบเทียบกับการผ่านconst string*(หรือconst string&) เนื่องจากstring*มีความหมายดังนี้:

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

เห็นได้ชัดว่ามีจุดประสงค์เพื่อส่งผ่านอาร์กิวเมนต์อาร์กิวเมนต์ตัวชี้แรกนั้นไม่จำเป็น

psหนึ่งในความแตกต่างทางการเงินย่อยระหว่างstd::string_viewและconst char*ถึงอย่างไรก็ตามคือ string_views ไม่จำเป็นต้องถูกยกเลิกด้วย null (มีขนาดในตัว) และสิ่งนี้ช่วยให้การสุ่มแบบต่อเนื่องของสตริงยาวขึ้น


4
downvotes คืออะไร std::string_views เป็นเพียงแฟนซีconst char*s ระยะเวลา GCC ดำเนินการดังนี้:class basic_string_view {const _CharT* _M_str; size_t _M_len;}
n.caillou

4
เพิ่งไปถึง 65K ตัวแทน (จาก 65 ในปัจจุบันของคุณ) และนี่จะเป็นคำตอบที่ยอมรับได้ (คลื่นไปยังฝูงชนสินค้า - ศาสนา) :)
mlvljr

7
std::string const*ผ่าน @mlvljr ไม่มีใคร และแผนภาพนั้นไม่สามารถเข้าใจได้ @ n.caillou: ความคิดเห็นของคุณมีความแม่นยำมากกว่าคำตอบอยู่แล้ว นั่นทำให้string_viewมากกว่า "แฟนซีchar const*" - มันค่อนข้างชัดเจนจริงๆ
sehe

@sehe ฉันอาจเป็นไปได้ว่าไม่มีใครไม่มีปัญหา (เช่นผ่านตัวชี้ (หรือการอ้างอิง) ไปยังสตริง const ทำไมไม่?) :)
mlvljr

2
@sehe คุณเข้าใจว่าจากมุมมองการเพิ่มประสิทธิภาพหรือการปฏิบัติstd::string const*และstd::string const&เหมือนกันใช่มั้ย
n.caillou
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.