มาตรฐาน C ++ มีประสิทธิภาพต่ำสำหรับ iostreams หรือฉันแค่จัดการกับการนำไปใช้ที่ไม่ดี?


197

ทุกครั้งที่ฉันพูดถึงประสิทธิภาพที่ช้าของ C ++ ไลบรารีมาตรฐาน iostreams ฉันจะได้พบกับคลื่นแห่งความไม่เชื่อ แต่ฉันมีผลลัพธ์ของ profiler ที่แสดงเวลาจำนวนมากที่ใช้ในโค้ดไลบรารี iostream (การปรับให้เหมาะสมเต็มที่ของคอมไพเลอร์), และการสลับจาก iostreams ไปเป็น I / O API เฉพาะระบบปฏิบัติการและการจัดการบัฟเฟอร์แบบกำหนดเอง

สิ่งที่เป็นงานพิเศษคือห้องสมุดมาตรฐาน C ++ ทำมันเป็นสิ่งจำเป็นโดยมาตรฐานและเป็นประโยชน์ในการปฏิบัติ? หรือคอมไพเลอร์บางตัวมีการใช้งาน iostreams ที่สามารถแข่งขันกับการจัดการบัฟเฟอร์ด้วยตนเองได้หรือไม่?

มาตรฐาน

เพื่อให้เรื่องต่าง ๆ มีความเคลื่อนไหวฉันได้เขียนโปรแกรมสั้น ๆ สองข้อเพื่อออกกำลังกายการบัฟเฟอร์ภายใน iostreams:

โปรดทราบว่าostringstreamและstringbufรุ่นต่างๆจะทำงานซ้ำน้อยลงเพราะมันช้ากว่ามาก

เมื่อวันที่ ideone ที่ostringstreamเป็นเรื่องเกี่ยวกับ 3 ครั้งช้ากว่าstd:copy+ back_inserter+ std::vectorและประมาณ 15 ครั้งช้ากว่าmemcpyเป็นบัฟเฟอร์ดิบ สิ่งนี้ให้ความรู้สึกที่สอดคล้องกับการทำโปรไฟล์ก่อนและหลังเมื่อฉันเปลี่ยนแอปพลิเคชันจริงเป็นการกำหนดบัฟเฟอร์เอง

สิ่งเหล่านี้เป็นบัฟเฟอร์ในหน่วยความจำดังนั้นความช้าของ iostreams จึงไม่สามารถถูกตำหนิบนดิสก์ช้า I / O, การล้างข้อมูลมากเกินไป, การซิงโครไนซ์กับ stdio หรือสิ่งอื่น ๆ ที่ผู้คนใช้ในการอ้างข้อสังเกตความช้าของไลบรารีมาตรฐาน C ++ iostream

มันจะเป็นการดีหากได้เห็นการวัดประสิทธิภาพของระบบอื่น ๆ และความเห็นเกี่ยวกับสิ่งที่การใช้งานทั่วไปทำ (เช่น gc's libc ++, Visual C ++, Intel C ++) และค่าใช้จ่ายที่กำหนดโดยมาตรฐาน

เหตุผลสำหรับการทดสอบนี้

ผู้คนจำนวนมากชี้ให้เห็นอย่างถูกต้องว่า iostreams ใช้กันอย่างแพร่หลายในรูปแบบเอาต์พุต อย่างไรก็ตามพวกเขายังเป็น API ที่ทันสมัยเท่านั้นที่มีให้โดยมาตรฐาน C ++ สำหรับการเข้าถึงไฟล์ไบนารี แต่เหตุผลที่แท้จริงสำหรับการทดสอบประสิทธิภาพในการบัฟเฟอร์ภายในนั้นใช้กับ I / O ที่จัดรูปแบบโดยทั่วไป: ถ้า iostreams ไม่สามารถเก็บดิสก์คอนโทรลเลอร์ที่ให้ข้อมูลดิบได้พวกเขาจะติดตามได้อย่างไรเมื่อพวกเขารับผิดชอบการจัดรูปแบบเช่นกัน?

กำหนดเวลาเกณฑ์มาตรฐาน

ทั้งหมดเหล่านี้เป็นแบบวนซ้ำของวงรอบนอก ( k)

บน ideone (gcc-4.3.4, ระบบปฏิบัติการที่ไม่รู้จักและฮาร์ดแวร์):

  • ostringstream: 53 มิลลิวินาที
  • stringbuf: 27 ms
  • vector<char>และback_inserter: 17.6 ms
  • vector<char> ด้วยตัววนซ้ำธรรมดา: 10.6 ms
  • vector<char> ตรวจสอบตัววนซ้ำและขอบเขต: 11.4 ms
  • char[]: 3.7 ms

บนแล็ปท็อปของฉัน (Visual C ++ 2010 x86,, cl /Ox /EHscWindows 7 Ultimate 64-bit, Intel Core i7, RAM 8 GB):

  • ostringstream: 73.4 มิลลิวินาที, 71.6 มิลลิวินาที
  • stringbuf: 21.7 ms, 21.3 ms
  • vector<char>และback_inserter: 34.6 ms, 34.4 ms
  • vector<char> ด้วยตัววนซ้ำธรรมดา: 1.10 ms, 1.04 ms
  • vector<char> การวนซ้ำและการตรวจสอบขอบเขต: 1.11 ms, 0.87 ms, 1.12 ms, 0.89 ms, 1.02 ms, 1.14 ms
  • char[]: 1.48 ms, 1.57 ms

Visual C ++ 2010 x86 กับโปรไฟล์-Guided Optimization cl /Ox /EHsc /GL /c, link /ltcg:pgiวิ่ง, link /ltcg:pgoวัด:

  • ostringstream: 61.2 ms, 60.5 ms
  • vector<char> ด้วยตัววนซ้ำธรรมดา: 1.04 ms, 1.03 ms

แล็ปท็อปเครื่องเดียวกันระบบปฏิบัติการเดียวกันโดยใช้ cygwin gcc 4.3.4 g++ -O3:

  • ostringstream: 62.7 ms, 60.5 ms
  • stringbuf: 44.4 ms, 44.5 ms
  • vector<char>และback_inserter: 13.5 ms, 13.6 ms
  • vector<char> ด้วยตัววนซ้ำธรรมดา: 4.1 ms, 3.9 ms
  • vector<char> ตรวจสอบตัววนซ้ำและขอบเขต: 4.0 ms, 4.0 ms
  • char[]: 3.57 ms, 3.75 ms

แล็ปท็อปเครื่องเดียวกัน Visual C ++ 2008 SP1 cl /Ox /EHsc:

  • ostringstream: 88.7 ms, 87.6 ms
  • stringbuf: 23.3 ms, 23.4 ms
  • vector<char>และback_inserter: 26.1 ms, 24.5 ms
  • vector<char> ด้วยตัววนซ้ำธรรมดา: 3.13 ms, 2.48 ms
  • vector<char> ตรวจสอบตัววนซ้ำและขอบเขต: 2.97 ms, 2.53 ms
  • char[]: 1.52 ms, 1.25 ms

แล็ปท็อปเครื่องเดียวกันคอมไพเลอร์ Visual C ++ 2010 64 บิต:

  • ostringstream: 48.6 ms, 45.0 ms
  • stringbuf: 16.2 ms, 16.0 ms
  • vector<char>และback_inserter: 26.3 ms, 26.5 ms
  • vector<char> ด้วยตัววนซ้ำธรรมดา: 0.87 ms, 0.89 ms
  • vector<char> ตรวจสอบตัววนซ้ำและขอบเขต: 0.99 มิลลิวินาที, 0.99 มิลลิวินาที
  • char[]: 1.25 ms, 1.24 ms

แก้ไข: ขับรถสองครั้งเพื่อดูว่าผลลัพธ์นั้นสอดคล้องกันเพียงใด IMO ที่สอดคล้องกันค่อนข้างสวย

หมายเหตุ: บนแล็ปท็อปของฉันเนื่องจากฉันสามารถสำรองเวลาของ CPU มากกว่าที่ ideone อนุญาตได้ฉันจึงกำหนดจำนวนการวนซ้ำเป็น 1,000 สำหรับวิธีการทั้งหมด ซึ่งหมายความว่าostringstreamและvectorจัดสรรซึ่งจะมีขึ้นเฉพาะในครั้งแรกผ่านควรมีผลกระทบเพียงเล็กน้อยเกี่ยวกับผลสุดท้าย

แก้ไข: อ๊ะพบข้อผิดพลาดในvector-with-common-iterator ตัววนซ้ำไม่ได้ก้าวหน้าดังนั้นจึงมีแคชจำนวนมากเกินไป ผมสงสัยว่าถูกดีกว่าvector<char> char[]มันไม่ได้สร้างความแตกต่างมากนัก แต่vector<char>ยังเร็วกว่าchar[]ภายใต้ VC ++ 2010

สรุปผลการวิจัย

การบัฟเฟอร์ของเอาต์พุตสตรีมจำเป็นต้องมีสามขั้นตอนในแต่ละครั้งที่มีการผนวกข้อมูล:

  • ตรวจสอบว่าบล็อกขาเข้าเหมาะสมกับพื้นที่บัฟเฟอร์ที่มีอยู่
  • คัดลอกบล็อกที่เข้ามา
  • อัพเดตตัวชี้การสิ้นสุดข้อมูล

ข้อมูลโค้ดล่าสุดที่ฉันโพสต์ "การทำvector<char>ซ้ำง่าย ๆ และการตรวจสอบขอบเขต" ไม่เพียงแค่ทำเช่นนี้ แต่ยังจัดสรรพื้นที่เพิ่มเติมและย้ายข้อมูลที่มีอยู่เมื่อบล็อกขาเข้าไม่พอดี ดังที่ Clifford ระบุไว้การบัฟเฟอร์ในคลาส I / O ไฟล์ไม่จำเป็นต้องทำเช่นนั้นมันจะล้างบัฟเฟอร์ปัจจุบันและนำกลับมาใช้ใหม่ ดังนั้นนี่ควรเป็นขอบเขตบนของต้นทุนของเอาต์พุตบัฟเฟอร์ และเป็นสิ่งที่จำเป็นในการทำให้บัฟเฟอร์ในหน่วยความจำทำงาน

ดังนั้นทำไมstringbuf2.5x ช้าลงบน ideone และอย่างน้อย 10 ครั้งช้าลงเมื่อฉันทดสอบ มันไม่ได้ถูกใช้งานแบบ polymorphically ใน micro-benchmark แบบง่ายๆดังนั้นมันจึงไม่ได้อธิบาย


24
คุณกำลังเขียนตัวอักษรหนึ่งล้านตัวต่อครั้งและสงสัยว่าทำไมมันช้ากว่าการคัดลอกไปยังบัฟเฟอร์ที่จัดสรรล่วงหน้า?
อานนท์

20
@Anon: ฉันบัฟเฟอร์สี่ล้านไบต์สี่ต่อครั้งและใช่ฉันสงสัยว่าทำไมช้า หากstd::ostringstreamไม่ฉลาดพอที่จะเพิ่มขนาดบัฟเฟอร์อย่างที่อธิบายได้std::vectorนั่นคือ (A) โง่และ (B) สิ่งที่ผู้คนคิดเกี่ยวกับประสิทธิภาพของ I / O ควรคิด อย่างไรก็ตามบัฟเฟอร์จะถูกนำกลับมาใช้ใหม่จะไม่ได้รับการจัดสรรใหม่ทุกครั้ง และstd::vectorยังใช้บัฟเฟอร์ที่เพิ่มขึ้นแบบไดนามิก ฉันพยายามที่จะเป็นธรรมที่นี่
Ben Voigt

14
คุณพยายามทำภารกิจอะไร หากคุณไม่ได้ใช้ใด ๆ ของคุณสมบัติการจัดรูปแบบของและคุณต้องการประสิทธิภาพได้อย่างรวดเร็วที่สุดเท่าที่ทำได้แล้วคุณควรพิจารณาจะตรงไปยังostringstream ชั้นเรียนจะคิดว่าจะผูกฟังก์ชั่นการจัดรูปแบบร่วมกันตระหนักถึงสถานที่ที่มีทางเลือกที่มีความยืดหยุ่นบัฟเฟอร์ (ไฟล์สตริง ฯลฯ ) ผ่านอินเตอร์เฟซและฟังก์ชั่นเสมือน หากคุณไม่ได้ทำการฟอร์แมตใด ๆ ดังนั้นการเพิ่มระดับของการอ้อมนั้นจะดูค่อนข้างแพงเมื่อเทียบกับวิธีการอื่น stringbufostreamrdbuf()
CB Bailey

5
+1 สำหรับ op ความจริง เราได้รับคำสั่งหรือเพิ่มความเร็วโดยการย้ายจากofstreamไปยังfprintfเมื่อส่งออกข้อมูลการบันทึกที่เกี่ยวข้องกับการเพิ่มเป็นสองเท่า MSVC 2008 บน WinXPsp3 iostreams เป็นเพียงสุนัขช้า
KitsuneYMG

6
นี่คือการทดสอบบางอย่างบนเว็บไซต์ของคณะกรรมการ: open-std.org/jtc1/sc22/wg21/docs/D_5.cpp
โยฮันเนส Schaub - litb

คำตอบ:


49

ไม่ตอบคำถามเฉพาะของคุณมากพอ ๆ กับชื่อเรื่อง: รายงานทางเทคนิคประจำปี 2549 เกี่ยวกับประสิทธิภาพของ C ++มีหัวข้อที่น่าสนใจบน IOStreams (หน้า 64) ส่วนใหญ่เกี่ยวข้องกับคำถามของคุณอยู่ในส่วน 6.1.2 ("ความเร็วการดำเนินการ"):

เนื่องจากลักษณะบางอย่างของการประมวลผล IOStreams มีการกระจายไปในหลายแง่มุมจึงปรากฏว่ามาตรฐานมีการใช้งานที่ไม่มีประสิทธิภาพ แต่นี่ไม่ใช่กรณี - โดยการใช้การประมวลผลล่วงหน้าบางรูปแบบสามารถหลีกเลี่ยงงานส่วนใหญ่ได้ ด้วยตัวเชื่อมโยงที่ชาญฉลาดกว่าปกติเล็กน้อยใช้เป็นไปได้ที่จะลบความไร้ประสิทธิภาพเหล่านี้ออก สิ่งนี้ถูกกล่าวถึงใน§6.2.3และ§6.2.5

นับตั้งแต่มีการเขียนรายงานในปีพ. ศ. 2549 เราหวังว่าจะมีการรวมเอาข้อเสนอแนะจำนวนมากไว้ในคอมไพเลอร์ปัจจุบัน แต่อาจไม่ใช่ในกรณีนี้

ในขณะที่คุณพูดถึง facets อาจไม่ปรากฏในwrite()(แต่ฉันจะไม่ถือว่าสุ่มสี่สุ่มห้า) ดังนั้นคุณสมบัติอะไร การใช้ GProf กับostringstreamโค้ดของคุณที่คอมไพล์ด้วย GCC จะทำให้เกิดการแยกย่อยดังต่อไปนี้:

  • 44.23% ใน std::basic_streambuf<char>::xsputn(char const*, int)
  • 34.62% ใน std::ostream::write(char const*, int)
  • 12.50% ใน main
  • 6.73% ใน std::ostream::sentry::sentry(std::ostream&)
  • 0.96% ใน std::string::_M_replace_safe(unsigned int, unsigned int, char const*, unsigned int)
  • 0.96% ใน std::basic_ostringstream<char>::basic_ostringstream(std::_Ios_Openmode)
  • 0.00% ใน std::fpos<int>::fpos(long long)

ดังนั้นจำนวนมากของเวลาที่ใช้xsputnซึ่งในที่สุดโทรstd::copy()หลังจากตรวจสอบจำนวนมากและการปรับปรุงตำแหน่งเคอร์เซอร์และบัฟเฟอร์ (ดูc++\bits\streambuf.tccรายละเอียดใน)

สิ่งที่ฉันทำคือคุณได้มุ่งเน้นไปที่สถานการณ์ที่เลวร้ายที่สุด การตรวจสอบทั้งหมดที่ดำเนินการจะเป็นเพียงส่วนเล็กน้อยของงานทั้งหมดที่ทำถ้าคุณจัดการกับข้อมูลจำนวนมากพอสมควร แต่รหัสของคุณเปลี่ยนข้อมูลเป็นสี่ไบต์ในแต่ละครั้งและทำให้เกิดค่าใช้จ่ายเพิ่มเติมทุกครั้ง เห็นได้ชัดว่าใครจะหลีกเลี่ยงการทำเช่นนั้นในสถานการณ์จริง - พิจารณาว่าความสำคัญน้อยเพียงใดที่จะได้รับโทษหากwriteถูกเรียกใช้อาร์เรย์ 1 ล้าน int แทนที่จะเป็น 1 ล้านครั้งในหนึ่ง int และในสถานการณ์ในชีวิตจริงเราจะต้องชื่นชมคุณสมบัติที่สำคัญของ IOStreams นั่นคือการออกแบบหน่วยความจำที่ปลอดภัยและประเภทที่ปลอดภัย ประโยชน์ดังกล่าวเกิดขึ้นในราคาหนึ่งและคุณได้ทำการทดสอบซึ่งทำให้ค่าใช้จ่ายเหล่านี้มีอิทธิพลต่อเวลาดำเนินการ


เสียงเหมือนข้อมูลที่ดีสำหรับคำถามในอนาคตเกี่ยวกับประสิทธิภาพของการแทรก / การแยกรูปแบบของ iostreams ซึ่งฉันอาจจะถามในไม่ช้า ostream::write()แต่ผมไม่เชื่อว่ามีแง่มุมที่เกี่ยวข้องกับ
Ben Voigt

4
+1 สำหรับการทำโปรไฟล์ (ซึ่งเป็นเครื่อง Linux ที่ฉันเข้าใจ) อย่างไรก็ตามฉันกำลังเพิ่มครั้งละสี่ไบต์ (จริง ๆ แล้วsizeof iแต่คอมไพเลอร์ทั้งหมดที่ฉันกำลังทดสอบมีขนาด 4 ไบต์int) และที่ไม่ได้ดูเหมือนทุกสิ่งที่ไม่สมจริงกับผมว่าขนาดชิ้นที่คุณคิดว่าจะได้รับการส่งผ่านไปในแต่ละการเรียกไปในรหัสทั่วไปเช่นxsputn stream << "VAR: " << var.x << ", " << var.y << endl;
Ben Voigt

39
@beldaz: ตัวอย่างโค้ด "ทั่วไป" ที่เรียกได้เพียงxsputnห้าครั้งเท่านั้นอาจอยู่ในลูปที่เขียนไฟล์ 10 ล้านบรรทัด การส่งผ่านข้อมูลไปยัง iostreams ในกลุ่มก้อนขนาดใหญ่นั้นมีสถานการณ์ในชีวิตจริงน้อยกว่าโค้ดมาตรฐานของฉันมาก เหตุใดฉันจึงต้องเขียนไปยังสตรีมที่บัฟเฟอร์ด้วยจำนวนการโทรขั้นต่ำ หากฉันต้องทำบัฟเฟอร์เองจุดประสงค์ของ iostreams คืออะไร? และด้วยข้อมูลไบนารีฉันมีตัวเลือกในการบัฟเฟอร์ด้วยตนเองเมื่อเขียนตัวเลขนับล้านไปยังไฟล์ข้อความตัวเลือกแบบกลุ่มไม่มีอยู่ฉันต้องโทรoperator <<หากัน
Ben Voigt

1
@beldaz: หนึ่งสามารถประเมินได้เมื่อ I / O เริ่มครอบงำด้วยการคำนวณอย่างง่าย ที่อัตราการเขียนเฉลี่ย 90 MB / s ซึ่งเป็นเรื่องปกติของฮาร์ดไดรฟ์ระดับผู้บริโภคปัจจุบันการล้างบัฟเฟอร์ 4MB ใช้เวลา <45ms (ปริมาณงานความหน่วงแฝงไม่สำคัญเนื่องจาก OS เขียนแคช) หากการรันลูปด้านในใช้เวลานานกว่านั้นในการเติมบัฟเฟอร์ซีพียูจะเป็นตัว จำกัด หากลูปด้านในทำงานได้เร็วขึ้น I / O จะเป็นตัว จำกัด หรืออย่างน้อยก็มีเวลา CPU เหลือสำหรับการทำงานจริง
Ben Voigt

5
แน่นอนว่านั่นไม่ได้หมายความว่าการใช้ iostreams จำเป็นต้องเป็นโปรแกรมที่ช้า หาก I / O เป็นส่วนเล็ก ๆ ของโปรแกรมดังนั้นการใช้ไลบรารี I / O ที่มีประสิทธิภาพต่ำจะไม่ส่งผลกระทบโดยรวมมากนัก แต่การถูกเรียกไม่บ่อยครั้งเพียงพอที่จะสำคัญนั้นไม่ได้เป็นเหมือนกับประสิทธิภาพที่ดีและใน I / O ที่มีการใช้งานหนัก
Ben Voigt

27

ฉันค่อนข้างผิดหวังกับผู้ใช้ Visual Studio ที่ออกไปซึ่งมีความรู้สึกในเรื่องนี้:

  • ในการดำเนินงานของ Visual Studio ostreamที่sentryวัตถุ (ซึ่งถูกต้องตามมาตรฐาน) เข้าสู่ส่วนที่สำคัญการปกป้องstreambuf(ซึ่งไม่จำเป็นต้องใช้) ดูเหมือนจะไม่เป็นตัวเลือกดังนั้นคุณจ่ายค่าใช้จ่ายในการซิงโครไนซ์เธรดแม้แต่สตรีมโลคัลที่ใช้โดยเธรดเดี่ยวซึ่งไม่จำเป็นต้องซิงโครไนซ์

รหัสนี้เจ็บที่ใช้ostringstreamในการจัดรูปแบบข้อความค่อนข้างรุนแรง การใช้ตัวstringbufหลีกเลี่ยงการใช้งานโดยตรงsentryแต่ตัวดำเนินการแทรกที่จัดรูปแบบแล้วไม่สามารถทำงานได้โดยตรงบนstreambufs สำหรับ Visual C ++ 2010 ส่วนสำคัญจะชะลอตัวลงostringstream::writeด้วยปัจจัยสามประการกับการstringbuf::sputnโทรพื้นฐาน

เมื่อดูที่ข้อมูลผู้แปลของ beldaz ใน newlibดูเหมือนว่า gcc sentryจะไม่ทำอะไรแบบนี้ ostringstream::writeภายใต้ gcc ใช้เวลานานกว่าประมาณ 50% stringbuf::sputnแต่stringbufตัวเองนั้นช้ากว่าภายใต้ VC ++ มาก และทั้งสองยังคงเปรียบเทียบที่ไม่พึงประสงค์อย่างมากกับการใช้การvector<char>บัฟเฟอร์ I / O แม้ว่าจะไม่เท่ากับระยะขอบเดียวกับใน VC ++


ข้อมูลนี้เป็นข้อมูลล่าสุดหรือไม่? การติดตั้ง AFAIK, C ++ 11 มาพร้อมกับ GCC ทำการล็อคแบบ 'บ้า' นี้ แน่นอนว่า VS2010 ก็ทำเช่นกัน ทุกคนสามารถอธิบายพฤติกรรมนี้และหาก 'ที่ไม่จำเป็นต้องใช้' ยังคงอยู่ใน C ++ 11 หรือไม่
mloskot

2
@mloskot: ฉันไม่เห็นความต้องการความปลอดภัยของเธรดในsentry... "ผู้ดูแลคลาสกำหนดคลาสที่รับผิดชอบในการทำคำนำหน้าข้อยกเว้นที่ปลอดภัยและการดำเนินการต่อท้าย" และหมายเหตุ "ตัวสร้างยามและ destructor ยังสามารถดำเนินการเพิ่มเติมขึ้นอยู่กับการใช้งาน" หนึ่งสามารถคาดการณ์จากหลักการ C ++ ของ "คุณไม่จ่ายเงินสำหรับสิ่งที่คุณไม่ได้ใช้" ที่คณะกรรมการ C ++ จะไม่อนุมัติข้อกำหนดที่สิ้นเปลืองเช่นนี้ แต่อย่าลังเลที่จะถามคำถามเกี่ยวกับความปลอดภัยของเธรด iostream
Ben Voigt

8

ปัญหาที่คุณเห็นคือทั้งหมดในค่าใช้จ่ายรอบการโทรแต่ละครั้งเพื่อเขียน () แต่ละระดับของสิ่งที่เป็นนามธรรมที่คุณเพิ่ม (อักขระ [] -> เวกเตอร์ -> สตริง -> ostringstream) เพิ่มการเรียก / ส่งคืนฟังก์ชันอีกสองสามครั้ง

ฉันแก้ไขสองตัวอย่างบน ideone เพื่อเขียนสิบ ints ในแต่ละครั้ง เวลา ostringstream เพิ่มขึ้นจาก 53 เป็น 6 ms (ปรับปรุงเกือบ 10 x) ในขณะที่ char loop ปรับปรุง (3.7 เป็น 1.5) - มีประโยชน์ แต่มีเพียงสองปัจจัยเท่านั้น

หากคุณกังวลเกี่ยวกับประสิทธิภาพคุณต้องเลือกเครื่องมือที่เหมาะสมสำหรับงาน ostringstream มีประโยชน์และยืดหยุ่น แต่มีบทลงโทษสำหรับการใช้วิธีที่คุณพยายามทำ char [] นั้นทำงานหนักขึ้น แต่ประสิทธิภาพที่เพิ่มขึ้นนั้นยอดเยี่ยม (โปรดจำไว้ว่า gcc อาจจะรวม memcpys ให้กับคุณเช่นกัน)

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


8
สิ่งที่ostringstream::write()ต้องทำนั้นvector::push_back()ไม่ได้? ถ้ามีอะไรมันควรจะเร็วกว่าเพราะมันส่งมอบบล็อกแทนที่จะเป็นองค์ประกอบสี่อย่าง หากostringstreamช้ากว่าstd::vectorโดยไม่ได้ให้คุณสมบัติเพิ่มเติมใด ๆ ใช่ฉันจะเรียกว่าใช้งานไม่ได้
Ben Voigt

1
@Ben Voigt: ในทางตรงกันข้ามเวกเตอร์ของมันต้องทำสิ่งที่ ostringstream ไม่ต้องทำนั่นทำให้เวกเตอร์มีประสิทธิภาพมากขึ้นในกรณีนี้ เวกเตอร์รับประกันได้ว่าจะต่อเนื่องกันในความทรงจำในขณะที่ ostringstream ไม่ใช่ Vector เป็นหนึ่งในคลาสที่ออกแบบมาเพื่อให้เป็นนักแสดงในขณะที่ ostringstream ไม่ใช่
Dragontamer5788

2
@Ben Voigt: การใช้stringbufโดยตรงจะไม่ลบการเรียกฟังก์ชั่นทั้งหมดเนื่องจากstringbufส่วนต่อประสานสาธารณะของฟังก์ชั่นประกอบด้วยฟังก์ชั่นที่ไม่ใช่แบบเสมือนในชั้นฐานซึ่งส่งไปยังฟังก์ชั่นเสมือนที่ได้รับการป้องกันในชั้นเรียนที่ได้รับ
CB Bailey

2
@Charles: สำหรับคอมไพเลอร์ที่ดีใด ๆ ที่ควรจะเป็นเนื่องจากการเรียกฟังก์ชั่นสาธารณะจะได้รับการแทรกเข้าไปในบริบทที่ทราบชนิดของไดนามิกที่คอมไพเลอร์ก็สามารถลบทิศทางและแม้กระทั่งการโทรแบบอินไลน์
Ben Voigt

6
@Roddy: ฉันควรคิดว่านี่เป็นรหัสเทมเพลตแบบอินไลน์ทั้งหมดสามารถมองเห็นได้ในทุกหน่วยการคอมไพล์ แต่ฉันเดาว่าอาจแตกต่างกันไปตามการนำไปใช้ สำหรับบางฉันคาดหวังว่าการโทรภายใต้การสนทนา, sputnฟังก์ชั่นสาธารณะที่เรียกว่าการป้องกันเสมือนที่xsputnจะถูกขีดเส้นใต้ แม้ว่าxsputnจะไม่ได้ถูก inline คอมไพเลอร์สามารถในขณะที่ inlining sputnกำหนดการxsputnแทนที่ที่จำเป็นและสร้างการโทรโดยตรงโดยไม่ต้องผ่าน vtable
Ben Voigt

1

เพื่อให้ได้ประสิทธิภาพที่ดีขึ้นคุณต้องเข้าใจว่าคอนเทนเนอร์ที่คุณใช้นั้นทำงานอย่างไร ในตัวอย่างอาร์เรย์ char [] ของคุณอาร์เรย์ของขนาดที่ต้องการถูกจัดสรรล่วงหน้า ในตัวอย่างเวกเตอร์และ ostringstream ของคุณคุณกำลังบังคับให้วัตถุจัดสรรซ้ำและจัดสรรใหม่และอาจคัดลอกข้อมูลหลายครั้งเมื่อวัตถุเติบโต

ด้วย std :: vector นี่จะแก้ไขได้อย่างง่ายดายโดยเริ่มต้นขนาดของเวกเตอร์เป็นขนาดสุดท้ายเมื่อคุณทำชุดอักขระ char; แต่คุณค่อนข้างจะทำลายประสิทธิภาพการทำงานโดยไม่เป็นธรรมโดยปรับขนาดเป็นศูนย์! นั่นเป็นการเปรียบเทียบที่ไม่ยุติธรรมเลย

ด้วยความเคารพต่อ ostringstream การจัดสรรพื้นที่ล่วงหน้านั้นเป็นไปไม่ได้ผมขอแนะนำให้ใช้งานอย่างไม่เหมาะสม ชั้นมียูทิลิตีมากกว่าชุดคำสั่งถ่านแบบง่ายๆ แต่ถ้าคุณไม่ต้องการยูทิลิตี้ตัวนั้นอย่าใช้มันเพราะคุณจะจ่ายค่าโสหุ้ยในทุกกรณี แต่ควรใช้สิ่งที่ดีสำหรับ - จัดรูปแบบข้อมูลลงในสตริง C ++ ให้บริการบรรจุภัณฑ์หลากหลายและ ostringstram เป็นหนึ่งในสิ่งที่เหมาะสมที่สุดสำหรับวัตถุประสงค์นี้

ในกรณีของเวกเตอร์และ ostringstream คุณจะได้รับการป้องกันจากบัฟเฟอร์ที่มากเกินไปคุณจะไม่ได้รับสิ่งนั้นด้วยอาเรย์ถ่านและการป้องกันนั้นไม่ได้มาฟรี


1
การจัดสรรไม่น่าจะเป็นปัญหาสำหรับ ostringstream เขาแค่ค้นหากลับไปที่ศูนย์เพื่อทำซ้ำในภายหลัง ไม่มีการตัดทอน ฉันก็ลองostringstream.str.reserve(4000000)แล้วมันก็ไม่ต่างอะไร
Roddy

ฉันคิดว่าด้วยostringstreamคุณสามารถ "จอง" โดยผ่านสตริงดัมมี่เช่น: ostringstream str(string(1000000 * sizeof(int), '\0'));ด้วยvectorการที่resizeไม่ได้ยกเลิกการจัดสรรพื้นที่ใด ๆ มันจะขยายออกหากจำเป็นเท่านั้น
Nim

1
"การป้องกันเวกเตอร์ .. จากบัฟเฟอร์โอเวอร์รัน" ความเข้าใจผิดที่พบบ่อย - vector[]ผู้ประกอบการมักจะไม่ตรวจสอบข้อผิดพลาดที่ขอบเขตโดยค่าเริ่มต้น vector.at()เป็นอย่างไร
Roddy

2
vector<T>::resize(0)มักจะไม่จัดสรรหน่วยความจำใหม่
Niki Yoshiuchi

2
@Roddy: ไม่ได้ใช้operator[]แต่push_back()(โดยวิธีback_inserter) ซึ่งแน่นอนว่าจะทดสอบล้น push_backที่เพิ่มเข้ามาอีกรุ่นที่ไม่ได้ใช้
Ben Voigt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.