ฉันกำลังดูSystematic Error Handling ใน C ++ - Andrei Alexandrescuเขาอ้างว่าข้อยกเว้นใน C ++ นั้นช้ามาก
นี่ยังคงเป็นจริงสำหรับ C ++ 98 หรือไม่?
ฉันกำลังดูSystematic Error Handling ใน C ++ - Andrei Alexandrescuเขาอ้างว่าข้อยกเว้นใน C ++ นั้นช้ามาก
นี่ยังคงเป็นจริงสำหรับ C ++ 98 หรือไม่?
คำตอบ:
โมเดลหลักที่ใช้ในปัจจุบันสำหรับข้อยกเว้น (Itanium ABI, VC ++ 64 บิต) คือข้อยกเว้นของโมเดล Zero-Cost
แนวคิดก็คือแทนที่จะเสียเวลาไปกับการตั้งยามและตรวจสอบอย่างชัดเจนว่ามีข้อยกเว้นทุกที่คอมไพเลอร์จะสร้างตารางด้านข้างที่แมปจุดใด ๆ ที่อาจทำให้เกิดข้อยกเว้น (ตัวนับโปรแกรม) ไปยังรายการตัวจัดการ เมื่อเกิดข้อยกเว้นรายการนี้จะได้รับการปรึกษาเพื่อเลือกตัวจัดการที่เหมาะสม (ถ้ามี) และสแต็กจะถูกคลายออก
เมื่อเทียบกับif (error)
กลยุทธ์ทั่วไป:
if
เมื่อมีข้อยกเว้นเกิดขึ้นอย่างไรก็ตามค่าใช้จ่ายไม่สำคัญที่จะวัด:
dynamic_cast
ทดสอบสำหรับตัวจัดการแต่ละตัว)ดังนั้นแคชส่วนใหญ่จึงพลาดและไม่สำคัญเมื่อเทียบกับรหัส CPU บริสุทธิ์
หมายเหตุ: สำหรับรายละเอียดเพิ่มเติมโปรดอ่านรายงาน TR18015 บทที่ 5.4 การจัดการข้อยกเว้น (pdf)
ใช่ข้อยกเว้นจะช้าในเส้นทางพิเศษแต่จะเร็วกว่าการตรวจสอบอย่างชัดเจน ( if
กลยุทธ์) โดยทั่วไป
หมายเหตุ: Andrei Alexandrescu ดูเหมือนจะตั้งคำถามว่า "เร็วกว่า" โดยส่วนตัวแล้วฉันได้เห็นสิ่งต่าง ๆ แกว่งไปมาทั้งสองวิธีบางโปรแกรมทำงานได้เร็วขึ้นโดยมีข้อยกเว้นและโปรแกรมอื่น ๆ เร็วกว่าด้วยสาขาดังนั้นจึงดูเหมือนว่าจะสูญเสียความสามารถในการมองโลกในแง่ดีไปในบางสภาวะ
มันสำคัญหรือไม่?
ฉันจะอ้างว่ามันไม่ ควรเขียนโปรแกรมโดยคำนึงถึงความสามารถในการอ่านไม่ใช่ประสิทธิภาพ (อย่างน้อยไม่ใช่เป็นเกณฑ์แรก) จะใช้ข้อยกเว้นเมื่อคาดว่าผู้โทรไม่สามารถหรือไม่ต้องการจัดการกับความล้มเหลวในจุดนั้นและส่งต่อไปยังสแต็ก โบนัส: ในข้อยกเว้น C ++ 11 สามารถแบ่งระหว่างเธรดโดยใช้ไลบรารีมาตรฐาน
นี่เป็นเรื่องละเอียดอ่อนฉันอ้างว่าmap::find
ไม่ควรโยน แต่ฉันสบายดีที่map::find
จะส่งคืนสิ่งchecked_ptr
ที่พ่นหากความพยายามที่จะหักล้างมันล้มเหลวเนื่องจากเป็นโมฆะในกรณีหลังเช่นเดียวกับในกรณีของคลาสที่ Alexandrescu แนะนำผู้โทรเลือกระหว่างการตรวจสอบอย่างชัดเจนและการอาศัยข้อยกเว้น การให้อำนาจผู้โทรโดยไม่ให้ความรับผิดชอบมากขึ้นมักเป็นสัญญาณของการออกแบบที่ดี
abort
จะช่วยให้คุณสามารถวัดขนาดไบนารีและตรวจสอบว่าเวลาโหลด / i-cache ทำงานในลักษณะเดียวกัน แน่นอนดีกว่าไม่ตีใด ๆabort
...
เมื่อคำถามถูกโพสต์ฉันกำลังเดินทางไปหาหมอโดยมีแท็กซี่รออยู่ฉันจึงมีเวลาเพียงแค่แสดงความคิดเห็นสั้น ๆ แต่ตอนนี้มีการแสดงความคิดเห็นและโหวตและโหวตแล้วฉันควรเพิ่มคำตอบของตัวเอง แม้ว่าคำตอบของ Matthieuจะค่อนข้างดีอยู่แล้ว
อ้างสิทธิ์อีกครั้ง
"ฉันกำลังดูการจัดการข้อผิดพลาดอย่างเป็นระบบใน C ++ - Andrei Alexandrescuเขาอ้างว่าข้อยกเว้นใน C ++ นั้นช้ามาก"
หากนั่นคือสิ่งที่ Andrei อ้างตามตัวอักษรแล้วครั้งหนึ่งเขาก็ทำให้เข้าใจผิดอย่างมากหากไม่ผิดอย่างจริงจัง สำหรับยก / ข้อยกเว้นโยนมักจะช้าเมื่อเทียบกับการดำเนินงานพื้นฐานอื่น ๆ ในภาษาที่ไม่คำนึงถึงการเขียนโปรแกรมภาษา ไม่ใช่แค่ใน C ++ หรือมากกว่าใน C ++ มากกว่าภาษาอื่น ๆ ตามที่อ้างว่าอ้างว่าระบุไว้
โดยทั่วไปโดยทั่วไปโดยไม่คำนึงถึงภาษาคุณลักษณะพื้นฐานของภาษาทั้งสองที่มีลำดับความสำคัญช้ากว่าที่เหลือเนื่องจากแปลเป็นการเรียกประจำที่จัดการโครงสร้างข้อมูลที่ซับซ้อนคือ
ยกเว้นการขว้างปาและ
การจัดสรรหน่วยความจำแบบไดนามิก
อย่างมีความสุขใน C ++ เรามักจะหลีกเลี่ยงทั้งรหัสเวลาที่สำคัญ
น่าเสียดายที่ไม่มีสิ่งเช่นนี้เป็นอาหารกลางวันฟรีแม้ว่าประสิทธิภาพเริ่มต้นของ C ++ จะค่อนข้างใกล้เคียง :-) เพื่อประสิทธิภาพที่ได้รับจากการหลีกเลี่ยงการโยนข้อยกเว้นและการจัดสรรหน่วยความจำแบบไดนามิกโดยทั่วไปทำได้โดยการเข้ารหัสที่ระดับนามธรรมที่ต่ำกว่าโดยใช้ C ++ เป็นเพียง "C ที่ดีกว่า" และความเป็นนามธรรมที่ต่ำลงหมายถึง“ ความซับซ้อน” ที่มากขึ้น
ความซับซ้อนที่มากขึ้นหมายถึงการใช้เวลาในการบำรุงรักษามากขึ้นและผลประโยชน์เพียงเล็กน้อยจากการใช้โค้ดซ้ำซึ่งเป็นต้นทุนทางการเงินที่แท้จริงแม้ว่าจะประเมินหรือวัดได้ยากก็ตาม กล่าวคือด้วย C ++ หนึ่งสามารถแลกเปลี่ยนประสิทธิภาพของโปรแกรมเมอร์บางส่วนเพื่อประสิทธิภาพในการดำเนินการได้หากต้องการ การที่จะทำเช่นนั้นส่วนใหญ่เป็นการตัดสินใจทางวิศวกรรมและทางความรู้สึกเพราะในทางปฏิบัติมีเพียงผลกำไรไม่ใช่ต้นทุนเท่านั้นที่สามารถประมาณและวัดผลได้ง่าย
ใช่ c ++ คณะกรรมการมาตรฐานสากลมีการเผยแพร่รายงานด้านเทคนิคเกี่ยวกับ C ++ ประสิทธิภาพ TR18015
ส่วนใหญ่หมายความว่าthrow
อาจใช้เวลานานมาก™เมื่อเทียบกับงานเช่นint
งานที่มอบหมายเนื่องจากการค้นหาตัวจัดการ
ดังที่ TR18015 กล่าวถึงในหัวข้อ 5.4“ ข้อยกเว้น” มีกลยุทธ์การดำเนินการจัดการข้อยกเว้นหลักสองประการ
วิธีการที่แต่ละtry
บล็อกตั้งค่าการจับข้อยกเว้นแบบไดนามิกเพื่อให้การค้นหาห่วงโซ่ไดนามิกของตัวจัดการดำเนินการเมื่อมีการโยนข้อยกเว้นและ
วิธีการที่คอมไพลเลอร์สร้างตารางการค้นหาแบบคงที่ซึ่งใช้เพื่อกำหนดตัวจัดการสำหรับข้อยกเว้นที่ถูกโยนทิ้ง
แนวทางแรกที่ยืดหยุ่นและทั่วไปเกือบจะถูกบังคับใน Windows 32 บิตในขณะที่ในที่ดิน 64 บิตและใน * nix-land มักใช้วิธีที่สองที่มีประสิทธิภาพมากกว่า
ตามที่รายงานกล่าวถึงสำหรับแต่ละแนวทางมีสามประเด็นหลักที่การจัดการข้อยกเว้นส่งผลกระทบต่อประสิทธิภาพ:
try
- บล็อก
ฟังก์ชันปกติ (โอกาสในการเพิ่มประสิทธิภาพ) และ
throw
- การแสดงออก
โดยหลักแล้วด้วยวิธีการจัดการข้อยกเว้นแบบไดนามิก (Windows 32 บิต) มีผลกระทบต่อtry
บล็อกโดยส่วนใหญ่ไม่คำนึงถึงภาษา (เนื่องจากสิ่งนี้ถูกบังคับโดยรูปแบบการจัดการข้อยกเว้นที่มีโครงสร้างของ Windows ) ในขณะที่วิธีตารางแบบคงที่มีค่าใช้จ่ายประมาณเป็นศูนย์สำหรับtry
- บล็อก การพูดคุยเรื่องนี้จะใช้พื้นที่และการค้นคว้ามากกว่าที่จะเป็นจริงสำหรับคำตอบ SO ดังนั้นโปรดดูรายละเอียดในรายงาน
น่าเสียดายที่รายงานจากปี 2549 มีการลงวันที่เล็กน้อยเมื่อปลายปี 2555 และเท่าที่ฉันรู้ว่าไม่มีอะไรเทียบได้ที่ใหม่กว่า
มุมมองที่สำคัญอีกประการหนึ่งคือผลกระทบของการใช้ข้อยกเว้นต่อประสิทธิภาพนั้นแตกต่างอย่างมากจากประสิทธิภาพที่แยกได้ของคุณลักษณะภาษาที่รองรับเนื่องจากตามที่รายงานบันทึกไว้
“ เมื่อพิจารณาถึงการจัดการข้อยกเว้นต้องเปรียบเทียบกับวิธีอื่นในการจัดการกับข้อผิดพลาด”
ตัวอย่างเช่น:
ค่าบำรุงรักษาเนื่องจากรูปแบบการเขียนโปรแกรมที่แตกต่างกัน (ความถูกต้อง)
การif
ตรวจสอบความล้มเหลวของไซต์การโทรซ้ำซ้อนเทียบกับส่วนกลางtry
ปัญหาในการแคช (เช่นรหัสที่สั้นกว่าอาจพอดีกับแคช)
รายงานมีรายการแง่มุมที่แตกต่างกันที่ต้องพิจารณา แต่อย่างไรก็ตามวิธีเดียวที่ใช้ได้จริงเพื่อให้ได้ข้อมูลที่ชัดเจนเกี่ยวกับประสิทธิภาพในการดำเนินการก็คือการใช้โปรแกรมเดียวกันโดยใช้ข้อยกเว้นและไม่ใช้ข้อยกเว้นภายในระยะเวลาการพัฒนาที่กำหนดและกับนักพัฒนา คุ้นเคยกับแต่ละวิธีแล้ววัด
ความถูกต้องมักจะสำคัญกว่าประสิทธิภาพ
สิ่งต่อไปนี้สามารถเกิดขึ้นได้อย่างง่ายดายโดยไม่มีข้อยกเว้น:
รหัส P บางตัวมีไว้เพื่อรับทรัพยากรหรือคำนวณข้อมูลบางอย่าง
รหัสการโทร C ควรตรวจสอบความสำเร็จ / ล้มเหลว แต่ไม่ได้
ทรัพยากรที่ไม่มีอยู่จริงหรือข้อมูลที่ไม่ถูกต้องถูกใช้ในโค้ดตามหลัง C ทำให้เกิดการทำร้ายร่างกายโดยทั่วไป
ปัญหาหลักคือจุด (2) โดยที่รหัสส่งคืนตามปกติรหัสการโทร C จะไม่ถูกบังคับให้ตรวจสอบ
มีสองวิธีหลักที่บังคับให้ตรวจสอบดังกล่าว:
โดยที่ P จะโยนข้อยกเว้นเมื่อล้มเหลวโดยตรง
โดยที่ P ส่งคืนอ็อบเจ็กต์ที่ C ต้องตรวจสอบก่อนใช้ค่าหลัก (มิฉะนั้นจะเป็นข้อยกเว้นหรือการยุติ)
แนวทางที่สองคือ AFAIK อธิบายโดย Barton และ Nackman เป็นครั้งแรกในหนังสือของพวกเขา * C ++ ทางวิทยาศาสตร์และวิศวกรรม: บทนำด้วยเทคนิคขั้นสูงและตัวอย่างซึ่งพวกเขาแนะนำคลาสที่เรียกว่าFallow
ผลลัพธ์ของฟังก์ชัน "เป็นไปได้" optional
ขณะนี้มีคลาสที่คล้ายกันซึ่งเรียกว่าไลบรารี Boost และคุณสามารถใช้Optional
คลาสด้วยตัวเองได้อย่างง่ายดายโดยใช้std::vector
as value carrier สำหรับกรณีของผลลัพธ์ที่ไม่ใช่ POD
ด้วยแนวทางแรกรหัสการโทร C ไม่มีทางเลือกอื่นนอกจากใช้เทคนิคการจัดการข้อยกเว้น อย่างไรก็ตามด้วยแนวทางที่สองรหัสการเรียก C สามารถตัดสินใจได้เองว่าจะทำการif
ตรวจสอบตามหรือการจัดการข้อยกเว้นทั่วไป ดังนั้นแนวทางที่สองสนับสนุนการทำให้โปรแกรมเมอร์เทียบกับการแลกเปลี่ยนประสิทธิภาพเวลาดำเนินการ
“ ฉันอยากรู้ว่านี่ยังเป็นจริงสำหรับ C ++ 98”
C ++ 98 เป็นมาตรฐาน C ++ แรก สำหรับข้อยกเว้นนั้นได้นำเสนอลำดับชั้นมาตรฐานของคลาสข้อยกเว้น (น่าเสียดายที่ค่อนข้างไม่สมบูรณ์) ผลกระทบหลักต่อประสิทธิภาพคือความเป็นไปได้ของข้อกำหนดข้อยกเว้น (ลบออกใน C ++ 11) ซึ่งไม่เคยนำไปใช้อย่างสมบูรณ์โดยคอมไพเลอร์ Windows C ++ หลัก Visual C ++: Visual C ++ ยอมรับไวยากรณ์ข้อกำหนดข้อยกเว้น C ++ 98 แต่เพียงละเว้น ข้อกำหนดข้อยกเว้น
C ++ 03 เป็นเพียงองค์ประกอบทางเทคนิคของ C ++ 98 เท่านั้นจริงๆใหม่ใน C ++ 03 คือการเริ่มต้นค่า ซึ่งไม่มีอะไรเป็นข้อยกเว้น.
ด้วยข้อกำหนดข้อยกเว้นทั่วไปมาตรฐาน C ++ 11 ถูกลบออกและแทนที่ด้วยnoexcept
คีย์เวิร์ด
มาตรฐาน C ++ 11 ยังเพิ่มการสนับสนุนสำหรับการจัดเก็บและการเปลี่ยนข้อยกเว้นซึ่งเหมาะอย่างยิ่งสำหรับการเผยแพร่ข้อยกเว้น C ++ ในการเรียกกลับของภาษา C การสนับสนุนนี้ จำกัด วิธีการจัดเก็บข้อยกเว้นปัจจุบันอย่างมีประสิทธิภาพ อย่างไรก็ตามเท่าที่ฉันรู้ว่าไม่มีผลต่อประสิทธิภาพการทำงานยกเว้นในระดับที่ในการจัดการข้อยกเว้นโค้ดรุ่นใหม่อาจใช้งานได้ง่ายขึ้นทั้งสองด้านของการเรียกกลับภาษา C
longjmp
ต้องใช้ตัวจัดการ
try..finally
สร้างสามารถดำเนินการได้โดยไม่ต้องคลี่คลายสแต็ค F #, C # และ Java ทั้งหมดใช้งานได้try..finally
โดยไม่ต้องใช้การคลายสแต็ก คุณเป็นเพียงlongjmp
ผู้จัดการ (ตามที่ฉันได้อธิบายไปแล้ว)
คุณไม่สามารถอ้างสิทธิ์เกี่ยวกับประสิทธิภาพได้เว้นแต่คุณจะแปลงรหัสเป็นแอสเซมบลีหรือเปรียบเทียบ
นี่คือสิ่งที่คุณเห็น: (ม้านั่งด่วน)
รหัสข้อผิดพลาดไม่ไวต่อเปอร์เซ็นต์ของการเกิดขึ้น ข้อยกเว้นมีค่าใช้จ่ายเล็กน้อยตราบเท่าที่ไม่เคยโยนทิ้ง เมื่อคุณโยนมันความทุกข์ยากก็เริ่มต้นขึ้น ในตัวอย่างนี้จะโยน 0%, 1%, 10%, 50% และ 90% ของเคส เมื่อข้อยกเว้นถูกโยน 90% ของเวลารหัสจะช้ากว่ากรณีที่มีการโยนข้อยกเว้น 10% ถึง 8 เท่า อย่างที่คุณเห็นข้อยกเว้นนั้นช้ามาก อย่าใช้หากโยนบ่อยๆ หากแอปพลิเคชันของคุณไม่มีข้อกำหนดแบบเรียลไทม์อย่าลังเลที่จะโยนพวกเขาหากเกิดขึ้นน้อยมาก
คุณเห็นความคิดเห็นที่ขัดแย้งกันมากมายเกี่ยวกับพวกเขา แต่สุดท้ายมีข้อยกเว้นช้าหรือไม่? ฉันไม่ตัดสิน. เพียงแค่ดูเกณฑ์มาตรฐาน
มันขึ้นอยู่กับคอมไพเลอร์
ตัวอย่างเช่น GCC เป็นที่ทราบกันดีว่ามีประสิทธิภาพต่ำมากเมื่อจัดการกับข้อยกเว้น แต่สิ่งนี้ดีขึ้นมากในช่วงไม่กี่ปีที่ผ่านมา
แต่โปรดทราบว่าข้อยกเว้นในการจัดการควรเป็นข้อยกเว้นมากกว่ากฎในการออกแบบซอฟต์แวร์ของคุณ เมื่อคุณมีแอปพลิเคชันที่มีข้อยกเว้นมากมายต่อวินาทีซึ่งส่งผลกระทบต่อประสิทธิภาพการทำงานและยังถือว่าเป็นการทำงานปกติคุณควรคิดที่จะทำสิ่งต่างๆ
ข้อยกเว้นเป็นวิธีที่ยอดเยี่ยมในการทำให้โค้ดอ่านง่ายขึ้นโดยนำโค้ดที่จัดการข้อผิดพลาดที่ไม่เป็นระเบียบออกไปให้พ้นทาง แต่ทันทีที่มันกลายเป็นส่วนหนึ่งของโฟลว์โปรแกรมปกติพวกเขาก็ยากที่จะปฏิบัติตาม จำไว้ว่า a throw
ค่อนข้างgoto catch
ปลอมตัว
throw new Exception
คือ Java-ism ตามกฎแล้วไม่ควรโยนพอยน์เตอร์
ใช่ แต่นั่นไม่สำคัญ ทำไม?
อ่านสิ่งนี้:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
โดยทั่วไปแล้วที่บอกว่าการใช้ข้อยกเว้นอย่าง Alexandrescu อธิบายไว้ (การชะลอตัว 50x เพราะใช้catch
เป็นelse
) นั้นไม่ถูกต้อง ที่กล่าวไว้สำหรับคนที่ชอบทำแบบนั้นฉันหวังว่า C ++ 22 :) จะเพิ่มสิ่งที่ต้องการ:
(โปรดทราบว่านี่จะต้องเป็นภาษาหลักเนื่องจากโดยทั่วไปแล้วคอมไพเลอร์สร้างโค้ดจากที่มีอยู่)
result = attempt<lexical_cast<int>>("12345"); //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
int x = result.get(); // or result.result;
}
else
{
// even possible to see what is the exception that would have happened in original function
switch (result.exception_type())
//...
}
ป.ล. โปรดทราบว่าแม้ว่าข้อยกเว้นจะช้าขนาดนั้น ... ก็ไม่ใช่ปัญหาหากคุณไม่ใช้เวลาส่วนนั้นมากในโค้ดระหว่างการดำเนินการ ... ตัวอย่างเช่นถ้าการแบ่ง float ช้าและคุณทำให้เป็น 4x เร็วกว่านั้นไม่สำคัญว่าคุณจะใช้เวลา 0.3% ในการหาร FP ...
เช่นเดียวกับในซิลิโคกล่าวว่าการใช้งานขึ้นอยู่กับ แต่โดยทั่วไปแล้วข้อยกเว้นนั้นถือว่าช้าสำหรับการนำไปใช้งานใด ๆ และไม่ควรใช้ในโค้ดที่เน้นประสิทธิภาพ
แก้ไข: ฉันไม่ได้บอกว่าอย่าใช้เลย แต่สำหรับโค้ดที่เน้นประสิทธิภาพควรหลีกเลี่ยง