ทุกครั้งที่ฉันพูดถึงประสิทธิภาพที่ช้าของ C ++ ไลบรารีมาตรฐาน iostreams ฉันจะได้พบกับคลื่นแห่งความไม่เชื่อ แต่ฉันมีผลลัพธ์ของ profiler ที่แสดงเวลาจำนวนมากที่ใช้ในโค้ดไลบรารี iostream (การปรับให้เหมาะสมเต็มที่ของคอมไพเลอร์), และการสลับจาก iostreams ไปเป็น I / O API เฉพาะระบบปฏิบัติการและการจัดการบัฟเฟอร์แบบกำหนดเอง
สิ่งที่เป็นงานพิเศษคือห้องสมุดมาตรฐาน C ++ ทำมันเป็นสิ่งจำเป็นโดยมาตรฐานและเป็นประโยชน์ในการปฏิบัติ? หรือคอมไพเลอร์บางตัวมีการใช้งาน iostreams ที่สามารถแข่งขันกับการจัดการบัฟเฟอร์ด้วยตนเองได้หรือไม่?
มาตรฐาน
เพื่อให้เรื่องต่าง ๆ มีความเคลื่อนไหวฉันได้เขียนโปรแกรมสั้น ๆ สองข้อเพื่อออกกำลังกายการบัฟเฟอร์ภายใน iostreams:
- วางข้อมูลไบนารีลงใน
ostringstream
http://ideone.com/2PPYw - ใส่ข้อมูลไบนารีลงใน
char[]
บัฟเฟอร์http://ideone.com/Ni5ct - วางข้อมูลไบนารีลงในhttp://ideone.com/Mj2Fi ที่
vector<char>
ใช้back_inserter
- ใหม่ : ตัว
vector<char>
วนซ้ำอย่างง่ายhttp://ideone.com/9iitv - ใหม่ : ใส่ข้อมูลไบนารีโดยตรงใน
stringbuf
http://ideone.com/qc9QA - ใหม่ : ตัว
vector<char>
วนซ้ำแบบง่ายพร้อมการตรวจสอบขอบเขตhttp://ideone.com/YyrKy
โปรดทราบว่า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 msvector<char>
และback_inserter
: 17.6 msvector<char>
ด้วยตัววนซ้ำธรรมดา: 10.6 msvector<char>
ตรวจสอบตัววนซ้ำและขอบเขต: 11.4 mschar[]
: 3.7 ms
บนแล็ปท็อปของฉัน (Visual C ++ 2010 x86,, cl /Ox /EHsc
Windows 7 Ultimate 64-bit, Intel Core i7, RAM 8 GB):
ostringstream
: 73.4 มิลลิวินาที, 71.6 มิลลิวินาทีstringbuf
: 21.7 ms, 21.3 msvector<char>
และback_inserter
: 34.6 ms, 34.4 msvector<char>
ด้วยตัววนซ้ำธรรมดา: 1.10 ms, 1.04 msvector<char>
การวนซ้ำและการตรวจสอบขอบเขต: 1.11 ms, 0.87 ms, 1.12 ms, 0.89 ms, 1.02 ms, 1.14 mschar[]
: 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 msvector<char>
ด้วยตัววนซ้ำธรรมดา: 1.04 ms, 1.03 ms
แล็ปท็อปเครื่องเดียวกันระบบปฏิบัติการเดียวกันโดยใช้ cygwin gcc 4.3.4 g++ -O3
:
ostringstream
: 62.7 ms, 60.5 msstringbuf
: 44.4 ms, 44.5 msvector<char>
และback_inserter
: 13.5 ms, 13.6 msvector<char>
ด้วยตัววนซ้ำธรรมดา: 4.1 ms, 3.9 msvector<char>
ตรวจสอบตัววนซ้ำและขอบเขต: 4.0 ms, 4.0 mschar[]
: 3.57 ms, 3.75 ms
แล็ปท็อปเครื่องเดียวกัน Visual C ++ 2008 SP1 cl /Ox /EHsc
:
ostringstream
: 88.7 ms, 87.6 msstringbuf
: 23.3 ms, 23.4 msvector<char>
และback_inserter
: 26.1 ms, 24.5 msvector<char>
ด้วยตัววนซ้ำธรรมดา: 3.13 ms, 2.48 msvector<char>
ตรวจสอบตัววนซ้ำและขอบเขต: 2.97 ms, 2.53 mschar[]
: 1.52 ms, 1.25 ms
แล็ปท็อปเครื่องเดียวกันคอมไพเลอร์ Visual C ++ 2010 64 บิต:
ostringstream
: 48.6 ms, 45.0 msstringbuf
: 16.2 ms, 16.0 msvector<char>
และback_inserter
: 26.3 ms, 26.5 msvector<char>
ด้วยตัววนซ้ำธรรมดา: 0.87 ms, 0.89 msvector<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 ไฟล์ไม่จำเป็นต้องทำเช่นนั้นมันจะล้างบัฟเฟอร์ปัจจุบันและนำกลับมาใช้ใหม่ ดังนั้นนี่ควรเป็นขอบเขตบนของต้นทุนของเอาต์พุตบัฟเฟอร์ และเป็นสิ่งที่จำเป็นในการทำให้บัฟเฟอร์ในหน่วยความจำทำงาน
ดังนั้นทำไมstringbuf
2.5x ช้าลงบน ideone และอย่างน้อย 10 ครั้งช้าลงเมื่อฉันทดสอบ มันไม่ได้ถูกใช้งานแบบ polymorphically ใน micro-benchmark แบบง่ายๆดังนั้นมันจึงไม่ได้อธิบาย
std::ostringstream
ไม่ฉลาดพอที่จะเพิ่มขนาดบัฟเฟอร์อย่างที่อธิบายได้std::vector
นั่นคือ (A) โง่และ (B) สิ่งที่ผู้คนคิดเกี่ยวกับประสิทธิภาพของ I / O ควรคิด อย่างไรก็ตามบัฟเฟอร์จะถูกนำกลับมาใช้ใหม่จะไม่ได้รับการจัดสรรใหม่ทุกครั้ง และstd::vector
ยังใช้บัฟเฟอร์ที่เพิ่มขึ้นแบบไดนามิก ฉันพยายามที่จะเป็นธรรมที่นี่
ostringstream
ชั้นเรียนจะคิดว่าจะผูกฟังก์ชั่นการจัดรูปแบบร่วมกันตระหนักถึงสถานที่ที่มีทางเลือกที่มีความยืดหยุ่นบัฟเฟอร์ (ไฟล์สตริง ฯลฯ ) ผ่านอินเตอร์เฟซและฟังก์ชั่นเสมือน หากคุณไม่ได้ทำการฟอร์แมตใด ๆ ดังนั้นการเพิ่มระดับของการอ้อมนั้นจะดูค่อนข้างแพงเมื่อเทียบกับวิธีการอื่น stringbuf
ostream
rdbuf()
ofstream
ไปยังfprintf
เมื่อส่งออกข้อมูลการบันทึกที่เกี่ยวข้องกับการเพิ่มเป็นสองเท่า MSVC 2008 บน WinXPsp3 iostreams เป็นเพียงสุนัขช้า