ข้อยกเว้น. NET มีความช้าเพียงใด


143

ฉันไม่ต้องการการสนทนาเกี่ยวกับเวลาและไม่โยนข้อยกเว้น ฉันต้องการแก้ไขปัญหาที่เรียบง่าย 99% ของเวลาที่การโต้เถียงไม่โยนข้อยกเว้นหมุนรอบตัวพวกเขาช้าในขณะที่อีกฝ่ายอ้างว่า (ด้วยการทดสอบเกณฑ์มาตรฐาน) ว่าความเร็วไม่ใช่ปัญหา ฉันได้อ่านบล็อกบทความและโพสต์มากมายที่เกี่ยวข้องกับด้านใดด้านหนึ่ง แล้วมันคืออะไร?

การเชื่อมโยงจากการตอบ: เป้าบิน , Mariani , Brumme


13
มีการโกหกการแช่งและการเปรียบเทียบ :)
gbjbaanb

น่าเสียดายที่คำตอบที่โหวตแล้วจำนวนมากในที่นี้พลาดไปที่คำถามนั้นถามว่า "ข้อยกเว้นมีความช้าแค่ไหน" และขอให้หลีกเลี่ยงหัวข้อที่ใช้บ่อย คำตอบง่ายๆสำหรับคำถามที่ถามจริงคือ ..... บน Windows CLR ข้อยกเว้นจะช้ากว่าค่าส่งคืน 750 เท่า
David Jeske

คำตอบ:


207

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

เพื่อให้ชัดเจน - ฉันไม่สนับสนุนการใช้ข้อยกเว้นที่ไม่สมเหตุสมผล ตัวอย่างเช่นint.TryParseเหมาะสมอย่างยิ่งสำหรับการแปลงข้อมูลจากผู้ใช้ เป็นเรื่องที่เหมาะสมเมื่ออ่านไฟล์ที่สร้างโดยเครื่องซึ่งความล้มเหลวหมายถึง "ไฟล์ไม่อยู่ในรูปแบบที่ควรจะเป็นผมไม่ต้องการจัดการกับไฟล์นี้เพราะผมไม่รู้ว่ามีอะไรผิดปกติ "

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


2
น่าเสียดายที่มีคนบอกว่าข้อยกเว้นนั้นฟรีใช้สำหรับฟังก์ชั่น "ถูกต้อง" เล็กน้อยพวกเขาควรจะใช้ตามที่คุณพูดเมื่อสิ่งต่าง ๆ ผิดพลาด - ในสถานการณ์ 'พิเศษ'
gbjbaanb

4
ใช่คนควรทราบอย่างแน่นอนว่ามีค่าใช้จ่ายประสิทธิภาพที่เกี่ยวข้องกับการใช้ข้อยกเว้นที่ไม่เหมาะสม ผมแค่คิดว่ามันไม่ใช่ปัญหาเมื่อพวกเขาจะใช้อย่างเหมาะสม :)
จอนสกีต

7
@ PaulLockwood: ฉันจะบอกว่าถ้าคุณมีข้อยกเว้นมากกว่า 200 ข้อต่อวินาทีคุณกำลังใช้ข้อยกเว้น เห็นได้ชัดว่าไม่ใช่เหตุการณ์ "พิเศษ" หากเกิดขึ้น 200 ครั้งต่อวินาที สังเกตประโยคสุดท้ายของคำตอบ: "โดยทั่วไปแล้วข้อยกเว้นไม่ควรเกิดขึ้นบ่อยครั้งจนกว่าคุณจะประสบปัญหาความถูกต้องอย่างมีนัยสำคัญและหากคุณประสบปัญหาความถูกต้องอย่างมีนัยสำคัญการทำงานไม่ใช่ปัญหาที่ใหญ่ที่สุดที่คุณเผชิญ"
Jon Skeet

4
@PaulLockwood: จุดของฉันคือว่าถ้าคุณได้มีข้อยกเว้น 200 + ต่อวินาทีที่อาจจะมีอยู่แล้วแสดงให้เห็นว่าคุณกำลังดูถูกข้อยกเว้น ไม่แปลกใจเลยที่มักจะเป็นอย่างนั้น แต่ก็หมายความว่าแง่มุมของการแสดงจะไม่เป็นสิ่งที่ฉันกังวลเป็นอันดับแรก - การใช้ข้อยกเว้นในทางที่ผิด เมื่อฉันลบข้อยกเว้นที่ไม่เหมาะสมทั้งหมดแล้วฉันจะไม่คาดหวังให้พวกเขาเป็นส่วนสำคัญของประสิทธิภาพ
Jon Skeet

4
@DavidJeske: คุณพลาดจุดคำตอบแล้ว เห็นได้ชัดว่าการโยนข้อยกเว้นนั้นช้ากว่าการคืนค่าปกติมาก ไม่มีใครโต้แย้งว่า คำถามคือพวกเขาช้าเกินไปหรือไม่ หากคุณอยู่ในสถานการณ์ที่เหมาะสมสำหรับการยกเว้นและนั่นเป็นสาเหตุของปัญหาด้านประสิทธิภาพแสดงว่าคุณอาจมีปัญหามากขึ้น - เพราะมันแสดงให้เห็นว่าระบบของคุณผิดปกติอย่างมาก ปกติที่เป็นปัญหาจริงๆว่าคุณกำลังใช้ข้อยกเว้นเมื่อพวกเขากำลังไม่เหมาะสมที่จะเริ่มต้นด้วย
Jon Skeet

31

มีคำตอบที่ชัดเจนสำหรับเรื่องนี้จากคนที่ใช้มัน - Chris Brumme เขาเขียนบทความบล็อกที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้ (คำเตือน - มันยาวมาก) (คำเตือน 2 - มันเขียนได้ดีมากถ้าคุณเป็นนักเทคนิกคุณจะอ่านจนจบและต้องเลิกทำงานหลังเลิกงาน :) )

บทสรุปผู้บริหาร: พวกเขาช้า พวกมันถูกนำมาใช้เป็นข้อยกเว้น Win32 SEH ดังนั้นบางคนจะผ่านขอบเขตของ ring 0 CPU! เห็นได้ชัดว่าในโลกแห่งความเป็นจริงคุณจะต้องทำงานอื่น ๆ อีกมากมายดังนั้นข้อยกเว้นแปลก ๆ จะไม่ถูกสังเกตเห็นเลย แต่ถ้าคุณใช้มันสำหรับโปรแกรมโฟลว์คาดว่าแอปของคุณจะถูกทุบ นี่เป็นอีกตัวอย่างหนึ่งของเครื่องการตลาด MS ที่ทำให้เราประสบความเสียหาย ฉันจำได้ว่ามีไมโครซอฟท์คนหนึ่งบอกเราว่าพวกเขามีปัญหาเรื่องค่าใช้จ่ายเป็นศูนย์อย่างสมบูรณ์ซึ่งเป็นสิ่งที่สมบูรณ์

Chris เสนอราคาที่เกี่ยวข้อง:

ในความเป็นจริง CLR ภายในใช้ข้อยกเว้นแม้ในส่วนที่ไม่มีการจัดการของเครื่องยนต์ อย่างไรก็ตามมีปัญหาประสิทธิภาพระยะยาวที่ร้ายแรงโดยมีข้อยกเว้นและสิ่งนี้จะต้องนำมาพิจารณาในการตัดสินใจของคุณ


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

8

ฉันไม่รู้ว่าสิ่งที่ผู้คนกำลังพูดถึงเมื่อพวกเขาบอกว่าพวกเขาช้าถ้าพวกเขาถูกโยน

แก้ไข: หากข้อยกเว้นไม่ได้ถูกโยนนั่นหมายความว่าคุณกำลังทำข้อยกเว้นใหม่ () หรืออะไรทำนองนั้น ไม่เช่นนั้นข้อยกเว้นจะทำให้เธรดหยุดทำงานชั่วคราวและสแต็กจะถูกเดิน สิ่งนี้อาจใช้ได้ในสถานการณ์ที่มีขนาดเล็กลง แต่ในเว็บไซต์ที่มีปริมาณการใช้งานสูงโดยอาศัยข้อยกเว้นเป็นกลไกเวิร์กโฟลว์หรือเส้นทางการดำเนินการจะทำให้เกิดปัญหาประสิทธิภาพการทำงาน ข้อยกเว้นนั้นไม่เลวและมีประโยชน์สำหรับการแสดงเงื่อนไขที่ยอดเยี่ยม

เวิร์กโฟลว์ข้อยกเว้นในแอป. NET ใช้ข้อยกเว้นโอกาสครั้งแรกและครั้งที่สอง สำหรับข้อยกเว้นทั้งหมดแม้ว่าคุณกำลังจับและจัดการกับวัตถุข้อยกเว้นยังคงถูกสร้างขึ้นและกรอบยังคงต้องเดินกองซ้อนเพื่อค้นหาตัวจัดการ หากคุณจับและตีกลับแน่นอนที่จะใช้เวลานาน - คุณจะได้รับการยกเว้นโอกาสครั้งแรกจับมันขึ้นมาก่อให้เกิดการยกเว้นครั้งแรกอีกครั้งซึ่งไม่พบผู้จัดการซึ่งทำให้เกิด ข้อยกเว้นโอกาสครั้งที่สอง

ข้อยกเว้นเป็นวัตถุบนฮีปด้วย - ดังนั้นหากคุณโยนข้อยกเว้นจำนวนมากแสดงว่าคุณมีปัญหาด้านประสิทธิภาพและหน่วยความจำ

นอกจากนี้ตามสำเนาของ "การทดสอบประสิทธิภาพ Microsoft .NET เว็บแอปพลิเคชัน" ของฉันที่เขียนโดยทีม ACE:

"การจัดการข้อยกเว้นมีราคาแพงการดำเนินการของเธรดที่เกี่ยวข้องจะถูกหยุดชั่วคราวในขณะที่ CLR เรียกคืนผ่านการเรียกสแต็กเพื่อค้นหาตัวจัดการข้อยกเว้นที่ถูกต้องและเมื่อพบตัวจัดการข้อยกเว้นและบล็อกจำนวนหนึ่งในท้ายที่สุด ก่อนการประมวลผลปกติสามารถทำได้ "

ประสบการณ์ของฉันในสนามแสดงให้เห็นว่าการลดข้อยกเว้นช่วยให้ประสิทธิภาพดีขึ้น แน่นอนว่ายังมีสิ่งอื่น ๆ ที่คุณต้องคำนึงถึงเมื่อทำการทดสอบประสิทธิภาพ - ตัวอย่างเช่นหากมีการถ่ายภาพ Disk I / O หรือคำสั่งของคุณอยู่ในไม่กี่วินาทีนั่นก็ควรจะเป็นจุดสนใจของคุณ แต่การค้นหาและลบข้อยกเว้นควรเป็นส่วนสำคัญของกลยุทธ์นั้น


1
ไม่มีสิ่งใดที่คุณเขียนขัดแย้งกับข้อเรียกร้องที่ยกเว้นจะช้าถ้าพวกเขาถูกโยน คุณพูดถึงสถานการณ์ที่พวกเขาถูกโยนทิ้งไปเท่านั้น เมื่อคุณ "ช่วยประสิทธิภาพการทำงานอย่างมีนัยสำคัญ" โดยการลบข้อยกเว้น: 1) พวกเขาเป็นจริงเงื่อนไขข้อผิดพลาดหรือเพียงแค่ข้อผิดพลาดผู้ใช้ ?
Jon Skeet

2) คุณทำงานภายใต้ดีบักเกอร์หรือไม่?
Jon Skeet

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

4
ฉันรู้ - ฉันเป็นส่วนหนึ่งของทีมพรีเมียร์ที่ MSFT :) สมมติว่ามาก - หลายพันวินาทีในบางกรณีที่เราเห็น ไม่มีอะไรที่เหมือนกับการเชื่อมต่อกับดีบักเกอร์สดและเพิ่งเห็นข้อยกเว้นที่เร็วที่สุดที่คุณสามารถอ่านได้ อดีตของช้า - ดังนั้นการเชื่อมต่อกับฐานข้อมูลดังนั้นคุณทำมันเมื่อมันทำให้รู้สึก
Cory Foy

5
Cory ฉันคิดว่าจุด "ช้าเฉพาะเมื่อพวกเขาถูกโยน" คือคุณไม่ต้องกังวลเกี่ยวกับประสิทธิภาพเนื่องจากการปรากฏตัวของบล็อก / ในที่สุดเท่านั้น คือสิ่งเหล่านี้ในตัวมันเองไม่ได้ทำให้เกิดผลกระทบต่อประสิทธิภาพ แต่เกิดขึ้นจากอินสแตนซ์ข้อยกเว้นจริงเท่านั้น
Ian Horwill

6

การโต้เถียงที่ฉันเข้าใจไม่ใช่ว่าการโยนข้อยกเว้นนั้นแย่มาก แต่มันเกี่ยวกับการใช้การสร้างแบบ throw / catch เป็นวิธีชั้นหนึ่งในการควบคุมตรรกะของแอปพลิเคชันปกติแทนการสร้างแบบมีเงื่อนไขแบบดั้งเดิมมากขึ้น

บ่อยครั้งที่แอปพลิเคชันตรรกะปกติคุณดำเนินการวนซ้ำที่การกระทำเดียวกันซ้ำหลายพัน / ล้านครั้ง ในกรณีนี้ด้วยการทำโปรไฟล์แบบง่าย ๆ (ดูคลาสนาฬิกาจับเวลา) คุณสามารถดูด้วยตัวคุณเองที่โยนข้อยกเว้นแทนที่จะพูดง่าย ๆ ถ้าคำสั่งสามารถกลายเป็นช้ากว่าอย่างมาก

ในความเป็นจริงฉันเคยอ่านว่าทีม. NET ที่ Microsoft ได้แนะนำวิธี TryXXXXX ใน. NET 2.0 ให้กับ FCL พื้นฐานหลายประเภทโดยเฉพาะเพราะลูกค้าบ่นว่าประสิทธิภาพของแอปพลิเคชันของพวกเขาช้ามาก

ปรากฎในหลายกรณีเนื่องจากลูกค้าพยายามแปลงค่าในลูปและการพยายามแต่ละครั้งล้มเหลว มีการโยนข้อยกเว้นการแปลงแล้วจับโดยตัวจัดการข้อยกเว้นที่จะกลืนข้อยกเว้นและทำการวนซ้ำต่อไป

Microsoft แนะนำวิธี TryXXX ควรใช้โดยเฉพาะอย่างยิ่งในสถานการณ์นี้เพื่อหลีกเลี่ยงปัญหาประสิทธิภาพที่เป็นไปได้

ฉันอาจจะผิด แต่ดูเหมือนว่าคุณไม่แน่ใจเกี่ยวกับความจริงของ "มาตรฐาน" ที่คุณได้อ่าน วิธีง่ายๆ: ลองด้วยตัวคุณเอง


ฉันคิดว่าภายใน "ลอง" ฟังก์ชั่นเหล่านั้นใช้ข้อยกเว้นเช่นกัน?
greg

1
ฟังก์ชัน "ลอง" เหล่านี้จะไม่ส่งข้อยกเว้นภายในสำหรับความล้มเหลวในการแยกวิเคราะห์ค่าอินพุต อย่างไรก็ตามพวกเขายังคงโยนข้อยกเว้นสำหรับสถานการณ์ข้อผิดพลาดอื่น ๆ เช่น ArgumentException
Ash

ฉันคิดว่าคำตอบนี้จะเข้าใกล้หัวใจของปัญหามากกว่าที่อื่น ๆ การพูดว่า 'ใช้ข้อยกเว้นเฉพาะในสถานการณ์ที่สมเหตุสมผล' ไม่ตอบคำถามจริงๆ - ความเข้าใจที่แท้จริงคือการใช้ข้อยกเว้น C # สำหรับโฟลว์การควบคุมนั้นช้ากว่าการสร้างตามเงื่อนไขปกติมาก คุณอาจได้รับการอภัยสำหรับการคิดเป็นอย่างอื่น ใน OCaml ข้อยกเว้นมีมากหรือน้อยกว่า GOTO และวิธีการยอมรับการใช้breakเมื่อใช้ฟีเจอร์ที่จำเป็น ในกรณีของฉันโดยเฉพาะการแทนที่ในวงวนวนมากint.Parse ()บวกลอง / จับเทียบกับint.TryParse ()ให้การเพิ่มประสิทธิภาพที่สำคัญ
Hugh W

4

เซิร์ฟเวอร์ XMPP ของฉันได้รับการเร่งความเร็วที่สำคัญ (ขออภัยไม่มีตัวเลขจริงสังเกตอย่างหมดจด) หลังจากที่ฉันพยายามป้องกันไม่ให้เกิดขึ้นอย่างสม่ำเสมอ (เช่นการตรวจสอบว่ามีการเชื่อมต่อซ็อกเก็ตก่อนที่จะพยายามอ่านข้อมูลเพิ่มเติม) (วิธีการ TryX ที่กล่าวถึง) นั่นคือมีผู้ใช้งานเสมือน (การสนทนา) เพียงประมาณ 50 คน


3
ตัวเลขจะมีประโยชน์โชคไม่ดี :( สิ่งต่าง ๆ เช่นการทำงานของซ็อกเก็ตควรเกินกว่าค่าใช้จ่ายในการยกเว้นอย่างแน่นอนเมื่อไม่ทำการดีบั๊กถ้าคุณเปรียบเทียบมันเต็มฉันจะสนใจดูผลลัพธ์จริงๆ
Jon Skeet

3

เพียงเพิ่มประสบการณ์ล่าสุดของตัวเองในการสนทนานี้: สอดคล้องกับสิ่งที่เขียนไว้ข้างต้นส่วนใหญ่ฉันพบว่าการโยนข้อยกเว้นให้ช้ามากเมื่อทำซ้ำหลายครั้งแม้ว่าจะไม่มีการดีบักเกอร์ก็ตาม ฉันเพิ่งเพิ่มประสิทธิภาพของโปรแกรมขนาดใหญ่ที่ฉันเขียนถึง 60% โดยการเปลี่ยนรหัสประมาณห้าบรรทัด: เปลี่ยนเป็นรุ่นส่งคืนรหัสแทนที่จะทิ้งข้อยกเว้น ได้รับรหัสในคำถามกำลังทำงานหลายพันครั้งและอาจโยนข้อยกเว้นนับพันก่อนที่ฉันจะเปลี่ยนมัน ดังนั้นฉันเห็นด้วยกับข้อความข้างต้น: โยนข้อยกเว้นเมื่อสิ่งที่สำคัญจริง ๆ ผิดไม่ใช่วิธีควบคุมการไหลของแอปพลิเคชันในสถานการณ์ "คาดหวัง" ใด ๆ


2

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

พวกเขามีมูลค่าการใช้รหัสข้อผิดพลาดแน่นอนข้อดีคือ IMO มากมาย


2

ฉันไม่เคยมีปัญหาเรื่องประสิทธิภาพกับข้อยกเว้น ฉันใช้ข้อยกเว้นจำนวนมาก - ฉันไม่เคยใช้รหัสส่งคืนหากฉันสามารถทำได้ พวกเขาเป็นแนวปฏิบัติที่ไม่ดีและในความคิดของฉันมีกลิ่นเหมือนปาเก็ตตี้

ฉันคิดว่ามันจะทำให้คุณใช้ข้อยกเว้น: ถ้าคุณใช้พวกเขาเช่นรหัสส่งคืน (แต่ละวิธีเรียกในสแต็คจับและ rethrows) จากนั้นใช่พวกเขาจะช้าเพราะคุณมีค่าใช้จ่ายแต่ละจับ / โยน

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

ในตอนท้ายของวันพวกเขาเป็นคุณสมบัติภาษาที่ถูกต้อง

เพียงเพื่อพิสูจน์ประเด็นของฉัน

กรุณาเรียกใช้รหัสที่ลิงค์นี้ (ใหญ่เกินไปสำหรับคำตอบ)

ผลลัพธ์ในคอมพิวเตอร์ของฉัน:

marco@sklivvz:~/develop/test$ mono Exceptions.exe | grep PM
10/2/2008 2:53:32 PM
10/2/2008 2:53:42 PM
10/2/2008 2:53:52 PM

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


2

แต่โมโนจะยกเว้นข้อผิดพลาดเร็วกว่าโหมด. net แบบสแตนด์อโลน 10 เท่าและโหมด. net แบบสแตนด์อโลนจะทำการยกเว้นยกเว้นเร็วกว่าโหมด. debugger 60x (เครื่องทดสอบมีซีพียูรุ่นเดียวกัน)

int c = 1000000;
int s = Environment.TickCount;
for (int i = 0; i < c; i++)
{
    try { throw new Exception(); }
    catch { }
}
int d = Environment.TickCount - s;

Console.WriteLine(d + "ms / " + c + " exceptions");

1

ในโหมดปล่อยค่าใช้จ่ายจะน้อยที่สุด

เว้นแต่คุณจะใช้ข้อยกเว้นสำหรับการควบคุมการไหล (ตัวอย่างเช่นการออกนอกท้องถิ่น) ในแบบเรียกซ้ำฉันสงสัยว่าคุณจะสังเกตเห็นความแตกต่างได้


1

บน Windows CLR สำหรับสายการโทรที่มีความลึก 8 การโยนข้อยกเว้นจะช้ากว่าการตรวจสอบและเผยแพร่ค่าส่งคืน 750 เท่า (ดูด้านล่างสำหรับมาตรฐาน)

นี้ค่าใช้จ่ายสูงสำหรับข้อยกเว้นเป็นเพราะทำงานร่วมหน้าต่าง CLR กับสิ่งที่เรียกว่าWindows ที่มีโครงสร้างการจัดการข้อยกเว้น สิ่งนี้ทำให้ข้อยกเว้นถูกจับและโยนอย่างถูกต้องในช่วงเวลาและภาษาที่ต่างกัน อย่างไรก็ตามมันช้ามาก

ข้อยกเว้นใน Mono runtime (บนแพลตฟอร์มใด ๆ ) นั้นเร็วกว่ามากเนื่องจากไม่ได้รวมเข้ากับ SEH อย่างไรก็ตามมีการสูญเสียการทำงานเมื่อส่งข้อยกเว้นข้ามหลายครั้งเนื่องจากไม่ได้ใช้อะไรอย่าง SEH

นี่คือผลลัพธ์โดยย่อจากเกณฑ์มาตรฐานของฉันยกเว้นค่าส่งคืนสำหรับ Windows CLR

baseline: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 13.0007 ms
baseline: recurse_depth 8, error_freqeuncy 0.25 (0), time elapsed 13.0007 ms
baseline: recurse_depth 8, error_freqeuncy 0.5 (0), time elapsed 13.0008 ms
baseline: recurse_depth 8, error_freqeuncy 0.75 (0), time elapsed 13.0008 ms
baseline: recurse_depth 8, error_freqeuncy 1 (0), time elapsed 14.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0 (0), time elapsed 13.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0.25 (249999), time elapsed 14.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0.5 (499999), time elapsed 16.0009 ms
retval_error: recurse_depth 5, error_freqeuncy 0.75 (999999), time elapsed 16.001 ms
retval_error: recurse_depth 5, error_freqeuncy 1 (999999), time elapsed 16.0009 ms
retval_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 20.0011 ms
retval_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 21.0012 ms
retval_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 24.0014 ms
retval_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 24.0014 ms
retval_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 24.0013 ms
exception_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 31.0017 ms
exception_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 5607.3208     ms
exception_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 11172.639  ms
exception_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 22297.2753 ms
exception_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 22102.2641 ms

และนี่คือรหัส ..

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

public class TestIt {
    int value;

    public class TestException : Exception { } 

    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    public bool baseline_null(bool shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            return shouldfail;
        } else {
            return baseline_null(shouldfail,recurse_depth-1);
        }
    }

    public bool retval_error(bool shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            if (shouldfail) {
                return false;
            } else {
                return true;
            }
        } else {
            bool nested_error = retval_error(shouldfail,recurse_depth-1);
            if (nested_error) {
                return true;
            } else {
                return false;
            }
        }
    }

    public void exception_error(bool shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            if (shouldfail) {
                throw new TestException();
            }
        } else {
            exception_error(shouldfail,recurse_depth-1);
        }

    }

    public static void Main(String[] args) {
        int i;
        long l;
        TestIt t = new TestIt();
        int failures;

        int ITERATION_COUNT = 1000000;


        // (0) baseline null workload
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

                failures = 0;
                DateTime start_time = DateTime.Now;
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    bool shoulderror = (i % EXCEPTION_MOD) == 0;
                    t.baseline_null(shoulderror,recurse_depth);
                }
                double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
                Console.WriteLine(
                    String.Format(
                      "baseline: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
                        recurse_depth, exception_freq, failures,elapsed_time));
            }
        }


        // (1) retval_error
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

                failures = 0;
                DateTime start_time = DateTime.Now;
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    bool shoulderror = (i % EXCEPTION_MOD) == 0;
                    if (!t.retval_error(shoulderror,recurse_depth)) {
                        failures++;
                    }
                }
                double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
                Console.WriteLine(
                    String.Format(
                      "retval_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
                        recurse_depth, exception_freq, failures,elapsed_time));
            }
        }

        // (2) exception_error
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

                failures = 0;
                DateTime start_time = DateTime.Now;
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    bool shoulderror = (i % EXCEPTION_MOD) == 0;
                    try {
                        t.exception_error(shoulderror,recurse_depth);
                    } catch (TestException e) {
                        failures++;
                    }
                }
                double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
                Console.WriteLine(
                    String.Format(
                      "exception_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
                        recurse_depth, exception_freq, failures,elapsed_time));         }
        }
    }
}


}

5
นอกเหนือจากจุดที่ขาดหายไปของคำถามโปรดอย่าใช้ DateTime ตอนนี้เป็นเกณฑ์มาตรฐานให้ใช้ Stopwatch ซึ่งออกแบบมาสำหรับการวัดเวลาที่ผ่านไป มันไม่ควรเป็นปัญหาที่นี่เพราะคุณวัดระยะเวลานานพอสมควร แต่มันก็คุ้มค่าที่จะติดนิสัย
Jon Skeet

ในทางตรงกันข้ามคำถามคือ "เป็นข้อยกเว้นช้า" ระยะเวลา มันถามโดยเฉพาะเพื่อหลีกเลี่ยงหัวข้อเมื่อจะโยนข้อยกเว้นเพราะหัวข้อนั้นปิดบังข้อเท็จจริง ประสิทธิภาพของข้อยกเว้นคืออะไร
David Jeske

0

บันทึกย่ออย่างย่อหนึ่งที่นี่เกี่ยวกับประสิทธิภาพที่เกี่ยวข้องกับการจับยกเว้น

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


1
อย่างไรก็ตามการปรากฏตัวของข้อยกเว้นสามารถส่งผลกระทบต่อการเพิ่มประสิทธิภาพ - วิธีการที่มีตัวจัดการข้อยกเว้นอย่างชัดเจนยากที่จะอินไลน์และการเรียงลำดับคำสั่งจะถูก จำกัด
Eamon Nerbonne

-1

เมื่อเขียนชั้นเรียน / ฟังก์ชั่นเพื่อให้ผู้อื่นใช้งานดูเหมือนเป็นการยากที่จะพูดเมื่อมีข้อยกเว้นที่เหมาะสม มีบางส่วนที่มีประโยชน์ของ BCL ที่ฉันต้องทิ้งและไปหา pinvoke เพราะพวกเขาโยนข้อยกเว้นแทนที่จะกลับข้อผิดพลาด สำหรับบางกรณีคุณสามารถหลีกเลี่ยงได้ แต่สำหรับคนอื่น ๆ เช่น System.Management และเคาน์เตอร์วัดประสิทธิภาพมีการใช้งานที่คุณต้องทำลูปซึ่งข้อยกเว้นจะถูกโยนโดย BCL บ่อยครั้ง

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

ในรหัสของฉันเองข้อยกเว้นจะถูกส่งออกไปเฉพาะเมื่อสิ่งต่าง ๆ เป็นพิเศษจนจำเป็นต้องดูที่การติดตามสแต็กและดูว่าเกิดข้อผิดพลาดอะไรจากนั้นจึงแก้ไข ดังนั้นฉันจึงค่อนข้างมีส่วนที่เขียนขึ้นใหม่ของ BCL เพื่อใช้การจัดการข้อผิดพลาดตามรูปแบบลอง .. แทนที่จะเป็นข้อยกเว้น


2
สิ่งนี้ดูเหมือนจะไม่พอดีกับคำพูดของผู้โพสต์ " ฉันไม่ต้องการการอภิปรายเกี่ยวกับคำสั่งเมื่อใดและจะไม่โยนข้อยกเว้น "
hrbrmstr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.