เป็นความคิดที่ดีที่จะบังคับให้มีการรวบรวมขยะเมื่อใด


135

ดังนั้นผมอ่านคำถามเกี่ยวกับการบังคับให้ C # เก็บขยะในการทำงานที่เกือบทุกคำตอบเดียวเหมือนกัน: คุณสามารถทำมัน แต่คุณไม่ควร - ยกเว้นบางกรณีที่หายากมาก น่าเศร้าที่ไม่มีใครมีรายละเอียดเกี่ยวกับกรณีเช่นนี้

คุณช่วยบอกฉันได้ไหมว่ามันเป็นความคิดที่ดีหรือสมเหตุสมผลในการจัดเรียงขยะ?

ฉันไม่ได้ขอเฉพาะกรณี C # แต่ทุกภาษาโปรแกรมที่มีตัวเก็บขยะ ฉันรู้ว่าคุณไม่สามารถบังคับให้ GC กับทุกภาษาเช่น Java ได้ แต่ลองสมมติว่าคุณทำได้


17
"แต่ค่อนข้างทุกภาษาที่ใช้โปรแกรมที่มีตัวรวบรวมขยะ"ภาษาที่แตกต่างกัน (หรือมีการใช้งานที่แตกต่างกันมากขึ้น) ใช้วิธีการต่าง ๆ สำหรับการรวบรวมขยะดังนั้นคุณจึงไม่น่าจะพบกฎขนาดเดียวที่เหมาะกับทุกคน
พันเอกสามสิบสอง

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

3
ฉันรู้สึกว่าถ้าคุณคาดว่าจะมีกำหนดส่งแบบเรียลไทม์อย่างหนักคุณจะไม่ใช้ภาษา GC ตั้งแต่แรก
GregRos

4
ฉันไม่เห็นว่าคุณสามารถตอบคำถามนี้ในแบบที่ไม่เฉพาะเจาะจงกับ VM ได้อย่างไร เกี่ยวข้องกับกระบวนการแบบ 32 บิตโดยไม่เกี่ยวข้องกับกระบวนการแบบ 64 บิต . NET JVMและสำหรับ บริษัท ระดับบน
rwong

3
@DavidConrad คุณสามารถบังคับได้ใน C # ดังนั้นคำถาม
โอเมก้า

คำตอบ:


127

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

คุณต้องรู้จักพฤติกรรมของ GC อย่างใกล้ชิดเพื่อทำสิ่งนี้ด้วยเหตุผลหรือเหตุผลใด ๆ

คำแนะนำเดียวในคอลเลกชันที่ฉันสามารถให้คือ: ไม่เคยทำมัน

หากคุณรู้รายละเอียดที่ซับซ้อนของ GC อย่างแท้จริงคุณจะไม่ต้องการคำแนะนำของฉันดังนั้นมันจะไม่สำคัญ หากคุณยังไม่ทราบด้วยความมั่นใจ 100% จะช่วยได้และต้องดูออนไลน์และค้นหาคำตอบเช่นนี้: คุณไม่ควรโทร GC.Collectหรืออีกวิธีหนึ่ง: คุณควรเรียนรู้รายละเอียดว่า GC ทำงานอย่างไร ทั้งภายในและภายนอกและเพียงแล้วคุณจะรู้คำตอบ

มีที่เดียวที่ปลอดภัยที่จะต้องใช้ GC.Collect :

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

การทำโปรไฟล์แบบนี้เป็นครั้งเดียวที่ฉันจะแนะนำให้รวบรวมทุกคนด้วยตนเอง


ตัวอย่างที่มีการจัดต่อไป

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

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


ถ้าอย่างนั้นเรามาพูดถึงซีแมนทิกส์และกลไกบางอย่างใน. NET GC ... หรือ ..

ทุกสิ่งที่ฉันคิดว่าฉันรู้เกี่ยวกับ. NET GC

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

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


แนวคิด GC

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

วัตถุในกอง Gen 0 มีชีวิตอยู่ผ่านคอลเลกชัน 0 รายการเหล่านี้ถูกสร้างขึ้นใหม่ดังนั้นเมื่อเร็ว ๆ นี้จึงไม่มีการรวบรวมเกิดขึ้นตั้งแต่การเริ่มต้น วัตถุในฮีป Gen 1 ของคุณมีชีวิตอยู่ผ่านคอลเล็กชั่นสะสมหนึ่งและไอเท็มในฮีป Gen 2 ของคุณก็มีชีวิตผ่านคอลเล็กชั่น 2 ใบ

ตอนนี้มันก็คุ้มค่าที่จะสังเกตว่ามันมีคุณสมบัติตามรุ่นและพาร์ติชั่นเหล่านี้ .NET GC รับรู้เฉพาะสามชั่วอายุคนเหล่านี้เท่านั้นเนื่องจากคอลเลกชันที่ส่งผ่าน heaps ทั้งสามนี้จะแตกต่างกันเล็กน้อย วัตถุบางอย่างอาจรอดจากการสะสมนับพันครั้ง GC เพิ่งทิ้งสิ่งเหล่านี้ไว้ที่อีกด้านหนึ่งของพาร์ติชั่นฮีป Gen 2, ไม่มีจุดใดที่จะแยกพาร์ติชั่นเหล่านั้นออกไปได้อีกเนื่องจากมันเป็น Gen 44 จริง ๆ ; การส่งผ่านคอลเลกชันจะเหมือนกับทุกอย่างในกอง Gen 2

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


มีอะไรในคอลเลกชัน

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

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

ตอนนี้ให้แนวคิดของหน่วยความจำ 3 ฮีปทั้งหมดที่แบ่งตามจำนวนคอลเล็กชันที่ผ่านมาเรามาพูดกันว่าทำไมพาร์ติชั่นเหล่านี้ถึงมีอยู่


การสะสม Gen 0

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


การสะสม Gen 1

Gen 1 ถูกวัตถุที่ไม่ได้ตกอยู่ในนี้มากหมวดหมู่ชั่วคราวของวัตถุอาจจะยังคงอาศัยอยู่ค่อนข้างสั้นเพราะ still- ส่วนใหญ่ของวัตถุที่สร้างขึ้นจะไม่ใช้เวลานาน ดังนั้น Gen 1 จึงเก็บสะสมค่อนข้างบ่อยอีกครั้งทำให้มันมีขนาดเล็กมากดังนั้นคอลเล็กชั่นจึงรวดเร็ว อย่างไรก็ตามสมมติฐานที่เป็นน้อยของวัตถุมันเป็นชั่วคราวกว่า Gen 0 จึงเก็บน้อยกว่า Gen 0

ฉันจะบอกว่าฉันไม่ทราบแน่ชัดว่ากลไกทางเทคนิคที่แตกต่างระหว่างการสะสมของ Gen 0 กับ Gen 1 นั้นมีอะไรนอกเหนือจากความถี่ที่พวกเขาเก็บรวบรวม


การสะสม Gen 2

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

เนื่องจาก Gen 2 เป็นถังสำหรับทุกสิ่งที่คนรุ่นอื่นไม่สามารถรวบรวมได้มันเป็นวัตถุส่วนใหญ่ที่ถาวรหรือมีอายุน้อยที่สุด ดังนั้นการตระหนักถึงสิ่งที่น้อยมากใน Gen 2 จริง ๆ แล้วจะเป็นสิ่งที่สามารถรวบรวมได้มันไม่จำเป็นต้องรวบรวมบ่อยครั้ง สิ่งนี้ทำให้คอลเลกชันของมันช้าลงเช่นกันเนื่องจากมันทำงานบ่อยครั้งมาก ดังนั้นนี่คือพื้นฐานที่พวกเขาได้จัดการกับพฤติกรรมพิเศษทั้งหมดสำหรับสถานการณ์แปลก ๆ เพราะพวกเขามีเวลาดำเนินการ


กองวัตถุขนาดใหญ่

ตัวอย่างหนึ่งของพฤติกรรมพิเศษ Gen 2 ก็คือว่ามันยังไม่คอลเลกชันบนกองวัตถุขนาดใหญ่ จนถึงตอนนี้ฉันได้พูดถึงเรื่อง Small Object Heap อย่างสมบูรณ์แล้ว แต่. NET runtime จะจัดสรรสิ่งต่าง ๆ ในขนาดที่แน่นอนให้เป็นฮีปแยกต่างหากเนื่องจากสิ่งที่ฉันเรียกว่าการบีบอัดด้านบน การบดอัดต้องมีการเคลื่อนย้ายวัตถุรอบ ๆ เมื่อการรวบรวมเสร็จสิ้นใน Small Object Heap หากมีวัตถุ 10mb ที่ยังมีชีวิตอยู่ใน Gen 1 มันจะต้องใช้เวลานานกว่านั้นในการทำการบดอัดให้เสร็จหลังจากการสะสมดังนั้นจึงทำให้การสะสมของ Gen 1 ช้าลง ดังนั้นวัตถุ 10mb จะถูกจัดสรรให้กับ Large Object Heap และถูกรวบรวมระหว่าง Gen 2 ซึ่งทำงานนาน ๆ ครั้ง


สรุป

อีกตัวอย่างคือวัตถุที่มี finalizers คุณวาง finalizer บนวัตถุที่อ้างอิงทรัพยากรเกินขอบเขตของ. NETs GC (ทรัพยากรที่ไม่มีการจัดการ) finalizer เป็นวิธีเดียวที่ GC เรียกร้องให้มีการรวบรวมทรัพยากรที่ไม่มีการจัดการ - คุณใช้ finalizer ของคุณเพื่อทำการรวบรวม / กำจัด / ปล่อยทรัพยากรที่ไม่มีการจัดการด้วยตนเองเพื่อให้แน่ใจว่าจะไม่รั่วไหลจากกระบวนการของคุณ เมื่อ GC เข้าสู่การดำเนินการ finalizer วัตถุของคุณแล้วการดำเนินการของคุณจะล้างทรัพยากรที่ไม่มีการจัดการทำให้ GC สามารถลบวัตถุของคุณโดยไม่เสี่ยงต่อการรั่วไหลของทรัพยากร

กลไกที่ finalizers ทำคือการอ้างอิงโดยตรงในคิวการสรุป เมื่อรันไทม์จัดสรรวัตถุด้วย finalizer มันจะเพิ่มตัวชี้ไปยังวัตถุนั้นในคิวการสรุปและล็อควัตถุของคุณให้เข้าที่ (เรียกว่าการปักหมุด) ดังนั้นการบีบอัดจะไม่ย้ายซึ่งจะเป็นการทำลายการอ้างอิงคิวสุดท้าย เมื่อการรวบรวมผ่านเกิดขึ้นในที่สุดวัตถุของคุณจะพบว่าไม่มีราก GC อีกต่อไป แต่ต้องดำเนินการเสร็จสิ้นก่อนจึงจะสามารถรวบรวมได้ ดังนั้นเมื่อวัตถุนั้นตายแล้วคอลเลคชั่นจะย้ายมันคือการอ้างอิงจากคิวการสรุปและวางการอ้างอิงไปยังสิ่งที่เรียกว่าคิว "FReachable" จากนั้นคอลเลกชันยังคงดำเนินต่อไป อีกครั้ง "ไม่กำหนด" ในอนาคต เธรดแยกที่เรียกว่าเธรด Finalizer จะผ่านคิว FReachable โดยดำเนินการ finalizers สำหรับแต่ละออบเจ็กต์ที่อ้างอิง หลังจากเสร็จสิ้นคิว FReachable จะว่างเปล่าและมีการพลิกบิตบนส่วนหัวของแต่ละวัตถุที่ระบุว่าพวกเขาไม่ต้องการการสรุป (บิตนี้สามารถพลิกด้วยตนเองด้วยGC.SuppressFinalizeซึ่งเป็นเรื่องธรรมดาในDispose()วิธีการ) ฉันยังสงสัยว่ามันไม่ได้ตรึงวัตถุ แต่ไม่ได้พูดถึงฉัน คอลเล็กชันถัดไปที่มากับสิ่งที่กองวัตถุนี้อยู่จะถูกรวบรวมในที่สุด คอลเลกชัน Gen 0 ไม่สนใจวัตถุที่เปิดใช้งานบิตสุดท้ายนั้นจะทำการโปรโมตมันโดยอัตโนมัติโดยไม่ต้องตรวจสอบหาราก วัตถุที่ไม่ได้ผ่านการรูทซึ่งต้องการการสรุปใน Gen 1 จะถูกโยนลงในFReachableคิว แต่คอลเลกชันจะไม่ทำสิ่งใดกับมันดังนั้นมันจึงอยู่ใน Gen 2 ด้วยวิธีนี้วัตถุทั้งหมดที่มี finalizer และไม่GC.SuppressFinalizeจะถูกรวบรวมใน Gen 2


4
@FlorianMargaine ใช่ ... พูดอะไรเกี่ยวกับ "GC" ทั่วทุกการใช้งานจริงๆไม่ได้ทำให้ความรู้สึก ..
จิมมี่ฮอฟฟา

10
tl; dr:ใช้พูลวัตถุแทน
Robert Harvey

5
tl; dr: สำหรับเวลา / การทำโปรไฟล์อาจมีประโยชน์
kutschkem

3
@ หลังจากอ่านคำอธิบายของฉันด้านบนของกลไก (เท่าที่ฉันเข้าใจพวกเขา) สิ่งที่เป็นประโยชน์ที่คุณเห็นมัน? คุณทำความสะอาดวัตถุจำนวนมาก - ใน SOH (หรือ LOH?) คุณเพิ่งทำให้เธรดอื่นหยุดชั่วคราวสำหรับคอลเลกชันนี้หรือไม่ คอลเลกชันนั้นเพิ่งโปรโมตวัตถุจำนวนมากไปยัง Gen 2 สองเท่าเมื่อล้างออกหรือไม่ การรวบรวมก่อให้เกิดการบีบอัดของ LOH (คุณเปิดไว้หรือไม่)? คุณมี heap GC จำนวนเท่าใดและ GC ของคุณอยู่ในโหมดเซิร์ฟเวอร์หรือเดสก์ท็อป? GC เป็นภูเขาน้ำแข็งน้ำแข็ง feckin 'การทรยศหักหลังอยู่ใต้น้ำ คัดท้ายชัดเจน ฉันไม่ฉลาดพอที่จะรวบรวมได้อย่างสะดวกสบาย
จิมมี่ฮอฟฟา

4
@RobertHarvey Object pool ไม่ใช่ bullet เงิน การสร้างตัวรวบรวมขยะ 0 เป็นกลุ่มวัตถุได้อย่างมีประสิทธิภาพ - โดยปกติแล้วจะมีขนาดให้พอดีกับระดับแคชที่เล็กที่สุดและโดยทั่วไปวัตถุใหม่จะถูกสร้างขึ้นในหน่วยความจำที่มีอยู่แล้วในแคช กลุ่มวัตถุของคุณกำลังแข่งขันกับสถานรับเลี้ยงเด็กของ GC สำหรับแคชและหากผลรวมของเรือนเพาะชำของ GC และกลุ่มของคุณมีขนาดใหญ่กว่าแคชคุณเห็นได้ชัดว่าคุณจะพลาดแคช และถ้าคุณวางแผนที่จะใช้ความเท่าเทียมตอนนี้คุณต้องนำการซิงโครไนซ์กลับมาใช้ใหม่และกังวลเกี่ยวกับการแบ่งปันที่ผิดพลาด
Doval

68

น่าเศร้าที่ไม่มีใครมีรายละเอียดเกี่ยวกับกรณีเช่นนี้

ฉันจะยกตัวอย่าง สรุปแล้วมันเป็นเรื่องยากที่การบังคับให้ GC เป็นความคิดที่ดี แต่มันก็คุ้มค่าจริงๆ คำตอบนี้มาจากประสบการณ์ของฉันกับ. NET และวรรณกรรม GC ควรพูดคุยกับแพลตฟอร์มอื่น ๆ เป็นอย่างดี (อย่างน้อยผู้ที่มี GC สำคัญ)

  • เกณฑ์มาตรฐานทุกชนิด คุณต้องการสถานะฮีปที่มีการจัดการที่รู้จักเมื่อเกณฑ์มาตรฐานเริ่มต้นเพื่อให้ GC ไม่ทริกเกอร์แบบสุ่มระหว่างการวัดประสิทธิภาพ เมื่อคุณทำซ้ำเบนช์มาร์กคุณต้องการให้จำนวนและปริมาณ GC เท่าเดิมในการทำซ้ำแต่ละครั้ง
  • ทรัพยากรอย่างฉับพลัน ตัวอย่างเช่นการปิดหน้าต่าง GUI ที่สำคัญหรือรีเฟรชแคช (ซึ่งจะเป็นการปล่อยเนื้อหาแคชเก่าที่มีขนาดใหญ่) GC ไม่สามารถตรวจจับสิ่งนี้ได้เนื่องจากสิ่งที่คุณกำลังทำคือการตั้งค่าการอ้างอิงถึง null ความจริงที่ว่าเด็กกำพร้ากราฟวัตถุทั้งหมดไม่สามารถตรวจพบได้ง่าย
  • การเปิดตัวของทรัพยากรที่ไม่มีการจัดการที่มีการรั่วไหลออกมา สิ่งนี้ไม่ควรเกิดขึ้นแน่นอน แต่ฉันเคยเห็นกรณีที่ห้องสมุดของบุคคลที่สามรั่วไหลสิ่งของ (เช่นวัตถุ COM) นักพัฒนาถูกบังคับให้ชักชวนคอลเลกชัน
  • โปรแกรมการโต้ตอบเช่นเกม ในระหว่างการเล่นเกมมีงบประมาณเวลาที่เข้มงวดมากต่อเฟรม (60Hz => 16ms ต่อเฟรม) เพื่อหลีกเลี่ยงการ hickups คุณจำเป็นต้องมีกลยุทธ์ในการจัดการกับ GCs หนึ่งในกลยุทธ์ดังกล่าวคือการหน่วงเวลา G2 GCs ให้มากที่สุดและบังคับพวกมันในเวลาที่เหมาะสมเช่นหน้าจอการโหลดหรือฉากที่ถูกตัด GC ไม่สามารถรู้ได้ว่าช่วงเวลาใดที่ดีที่สุด
  • การควบคุมความล่าช้าโดยทั่วไป เว็บแอปพลิเคชั่นบางตัวปิดการใช้งาน GCs และเรียกใช้ G2 คอลเลกชันเป็นระยะในขณะที่ถูกเปลี่ยนจากการหมุนตัวถ่วง วิธีนี้ทำให้การหน่วงเวลา G2 ไม่เคยปรากฏขึ้นกับผู้ใช้

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

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

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

โปรดทราบว่าค่าใช้จ่าย GC แตกต่างกันไปตามขนาดฮีปและจำนวนการอ้างอิงในฮีป ในกองเล็ก ๆ ค่าใช้จ่ายอาจมีขนาดเล็กมาก ฉันเคยเห็นอัตราการรวบรวม G2 ด้วย .NET 4.5 จาก 1-2GB / วินาทีในแอปที่ใช้งานจริงที่มีขนาดฮีป 1GB


สำหรับกรณีการควบคุมเวลาในการตอบสนองฉันคิดว่าแทนที่จะทำเช่นนี้เป็นระยะ ๆ คุณสามารถทำได้โดยต้องการ (เช่นเมื่อการใช้หน่วยความจำเพิ่มขึ้นเกินเกณฑ์ที่กำหนด)
Paŭlo Ebermann

3
+1 สำหรับย่อหน้าที่สองถึงย่อหน้าสุดท้าย บางคนมีความรู้สึกเดียวกันเกี่ยวกับคอมไพเลอร์และรวดเร็วในการเรียกเกือบทุกอย่าง "การเพิ่มประสิทธิภาพก่อนวัยอันควร" ฉันมักจะบอกสิ่งที่คล้ายกัน
Honza Brabec

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

1
@HonzaBrabec ปัญหาเหมือนกันในทั้งสองกรณี: ถ้าคุณคิดว่าคุณรู้ดีกว่า GC หรือคอมไพเลอร์มันก็ง่ายมากที่จะทำร้ายตัวเอง หากคุณรู้มากกว่านี้จริง ๆ คุณจะได้รับประโยชน์สูงสุดเมื่อคุณรู้ว่ายังไม่ถึงกำหนด
svick

27

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

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


12

สิ่งหนึ่งที่ไม่มีใครได้กล่าวถึงก็คือว่าในขณะที่ใช้ Windows GC เป็นสิ่งที่ดีที่น่าอัศจรรย์ใจ GC บน Xbox คือขยะ(เล่นสำนวนเจตนา)

ดังนั้นเมื่อเขียนโปรแกรมเกม XNA ที่ตั้งใจจะทำงานบน XBox มันสำคัญอย่างยิ่งที่จะต้องมีการเก็บขยะในช่วงเวลาที่เหมาะสมหรือคุณจะมีอาการสะอึก FPS เป็นระยะ ๆ นอกจากนี้ใน XBox เป็นเรื่องปกติที่จะใช้structวิธีการบ่อยครั้งกว่าที่คุณต้องการเพื่อลดจำนวนวัตถุที่ต้องรวบรวมขยะ


4

การรวบรวมขยะเป็นอันดับแรกและสำคัญที่สุดคือเครื่องมือการจัดการหน่วยความจำ ดังนั้นตัวรวบรวมขยะจะรวบรวมเมื่อมีแรงกดดันหน่วยความจำ

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

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

ในกรณีเหล่านี้คุณอาจเรียกใช้การรวบรวมขยะด้วยตนเองเมื่อคุณทราบว่ามีการใช้ทรัพยากรที่ไม่ใช่หน่วยความจำที่มีค่า

RMI

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

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

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

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


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

และตามที่ระบุไว้ในคำตอบเมื่อมีcloseวิธีการ (หรือทรัพยากรสามารถใช้กับusingบล็อก) เหล่านี้เป็นวิธีการที่เหมาะสม คำตอบนั้นเกี่ยวข้องกับกรณีที่ไม่ค่อยเกิดขึ้นซึ่งกลไกเหล่านี้ไม่สามารถใช้ได้
James_pic

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

@Jules ฉันเห็นด้วย แต่บางครั้งก็หลีกเลี่ยงไม่ได้ บางครั้ง abstractions รั่วและการใช้สิ่งที่เป็นนามธรรมนั้นดีกว่าการไม่ใช้สิ่งที่เป็นนามธรรม บางครั้งคุณต้องทำงานกับรหัสเดิมที่คุณต้องทำตามสัญญาที่คุณรู้ว่าคุณไม่สามารถรักษาได้ ใช่มันหายากและควรหลีกเลี่ยงถ้าเป็นไปได้และมีเหตุผลที่มีคำเตือนเหล่านี้ทั้งหมดเกี่ยวกับการบังคับให้เก็บขยะ แต่สถานการณ์เหล่านี้เกิดขึ้นและ OP ได้ถามว่าสถานการณ์เหล่านี้เป็นอย่างไร - ซึ่งฉันตอบไปแล้ว .
James_pic

2

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

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

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

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

คุณอาจทำการทดสอบเคส (หลังจากระมัดระวัง) ว่าคุณควรบังคับให้มีการรวบรวมขยะเต็มหลังจากคุณประมวลผลแต่ละไฟล์

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

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

ฉันอยากจะมี API การรวบรวมขยะเมื่อฉันสามารถบอกใบ้เกี่ยวกับสิ่งประเภทนี้ได้โดยไม่ต้องบังคับให้ GC เป็นตัวของตัวเอง

ดูเพิ่มเติมที่ " Performance Tidbits ของ Rico Mariani "


2

มีหลายกรณีที่คุณอาจต้องการโทรหา gc () ด้วยตนเอง

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

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

1
ฉันเดาว่าคุณมีคะแนนลงคะแนนที่สาม อาจเป็นเพราะการพูดว่า "นี่เป็นเพียงแค่สามัญสำนึก"
immibis

2
เมื่อคุณสร้างกลุ่มวัตถุขนาดใหญ่ GC ควรฉลาดพอที่จะรู้ว่าจำเป็นต้องมีชุดสะสม เช่นเดียวกันเมื่อหน่วยความจำจะต้องมีการบีบอัด การใช้ GC เพื่อเพิ่มประสิทธิภาพตำแหน่งหน่วยความจำของวัตถุที่เกี่ยวข้องนั้นไม่น่าเชื่อถือ ฉันคิดว่าคุณสามารถหาวิธีแก้ปัญหาอื่น ๆ (struct, ไม่ปลอดภัย, ... ) (ฉันไม่ใช่ผู้ลงคะแนน)
Guillaume

3
ความคิดแรกของคุณเกี่ยวกับเวลาที่ใช้ได้คือคำแนะนำที่แย่ในความคิดของฉัน มีโอกาสสูงที่มีการสะสมเมื่อไม่นานมานี้ดังนั้นความพยายามของคุณในการเก็บรวบรวมอีกครั้งนั้นเป็นเพียงการส่งเสริมวัตถุโดยพลการให้กับคนรุ่นหลังซึ่งเป็นเรื่องที่ไม่ดีอยู่เสมอ รุ่นต่อ ๆ มามีคอลเลกชันที่ใช้เวลานานในการเริ่มต้นเพิ่มขนาดฮีปของพวกเขา "เพื่อล้างพื้นที่ให้มากที่สุด" เพียงแค่ทำให้สิ่งนี้มีปัญหามากขึ้น นอกจากนี้หากคุณกำลังจะเพิ่มแรงกดดันหน่วยความจำด้วยการโหลดคุณมีแนวโน้มที่จะเริ่มกระตุ้นคอลเลกชันต่อไปซึ่งจะทำงานช้าลงเพราะเพิ่ม Gen1 / 2
Jimmy Hoffa

2
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.หากคุณจัดสรรวัตถุเป็นตันในอัตราต่อรองพวกเขาจะถูกบีบอัดไว้แล้ว หากมีสิ่งใดการเก็บขยะอาจทำให้พวกเขารอบเล็กน้อย ไม่ว่าจะด้วยวิธีใดการใช้โครงสร้างข้อมูลที่หนาแน่นและไม่กระโดดไปมาในหน่วยความจำแบบสุ่มจะส่งผลกระทบที่ใหญ่กว่า หากคุณกำลังใช้รายการลิงก์แบบองค์ประกอบเดียวต่อโหนดจะไม่มีการใช้กลอุบาย GC จำนวนเท่าใดสำหรับการทำเช่นนั้น
Doval

2

ตัวอย่างโลกแห่งความจริง:

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

สิ่งที่ชัดเจนพอที่จะทำที่นี่คือการโหลดกราฟที่เกี่ยวข้องลงในหน่วยความจำและเข้าถึงจากที่นั่นแทนที่จะเป็นฐานข้อมูลอัปเดตกราฟเมื่อ DB เปลี่ยนแปลง

แต่เนื่องจากมีขนาดใหญ่มากการโหลดที่ไร้เดียงสาจะต้องใช้หน่วยความจำอย่างน้อย 6GB พร้อมกับข้อมูลที่จะเพิ่มขึ้นในอนาคต (ฉันไม่มีตัวเลขที่แน่นอนเมื่อเห็นได้ชัดว่าเครื่อง 2GB ของฉันพยายามที่จะรับมือกับอย่างน้อย 6GB ฉันมีการวัดทั้งหมดที่ฉันต้องรู้ว่ามันจะไม่ทำงาน)

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

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

ดังนั้นการเรียกเป็นระยะGC.Collect()ในระหว่างกระบวนการสร้างนี้หมายความว่าสิ่งทั้งหมดทำงานได้อย่างราบรื่น แน่นอนว่าฉันไม่ได้โทรติดต่อด้วยตนเองGC.Collect()ตลอดเวลาที่โปรแกรมทำงาน

กรณีจริงของโลกนี้เป็นตัวอย่างที่ดีของแนวทางเมื่อเราควรใช้GC.Collect():

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

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


0

ฉันมีการใช้งานสำหรับการกำจัดขยะซึ่งค่อนข้างแหกคอก

มีการปฏิบัติที่ผิดซึ่งเป็นที่น่าเสียดายที่แพร่หลายมากในโลก C #, การดำเนินการกำจัดวัตถุที่ใช้น่าเกลียด clunky, งดงามและข้อผิดพลาดง่ายสำนวนที่รู้จักในฐานะเป็นIDisposable-ทิ้ง MSDN อธิบายความยาวและผู้คนจำนวนมากสาบานตามมันอย่างเคร่งครัดใช้เวลาหลายชั่วโมงในการพูดคุยกันอย่างแม่นยำว่าควรทำอย่างไร ฯลฯ

(โปรดทราบว่าสิ่งที่ฉันเรียกว่าน่าเกลียดที่นี่ไม่ใช่รูปแบบการกำจัดวัตถุเองสิ่งที่ฉันเรียกว่าน่าเกลียดเป็นIDisposable.Dispose( bool disposing )สำนวนเฉพาะ)

สำนวนนี้ถูกประดิษฐ์ขึ้นเพราะมันเป็นไปไม่ได้ที่จะรับประกันได้ว่าตัวทำลายวัตถุของคุณจะถูกเรียกใช้โดยตัวรวบรวมขยะเพื่อล้างทรัพยากรดังนั้นผู้คนจึงทำการล้างทรัพยากรภายในIDisposable.Dispose()และในกรณีที่พวกเขาลืมพวกเขายังลองอีกครั้ง ภายใน destructor คุณก็รู้ในกรณี

แต่แล้วคุณIDisposable.Dispose()อาจมีทั้งวัตถุที่มีการจัดการและไม่มีการจัดการในการล้าง แต่วัตถุที่มีการจัดการไม่สามารถล้างได้เมื่อIDisposable.Dispose()ถูกเรียกใช้จากภายใน destructor เพราะพวกเขาได้รับการดูแลโดยตัวเก็บขยะ ณ จุดนั้นในเวลานั้น จำเป็นต้องใช้Dispose()วิธีแยกต่างหากที่ยอมรับการbool disposingตั้งค่าสถานะเพื่อทราบว่าควรล้างวัตถุทั้งที่ได้รับการจัดการและไม่มีการจัดการหรือเฉพาะวัตถุที่ไม่มีการจัดการ

ขอโทษนะที่นี่เป็นบ้า

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

ตอนนี้อย่างเคร่งครัดพูดมันเป็นไปไม่ได้แน่นอนที่จะรับประกันว่าไม่มีโปรแกรมเมอร์จะเคยทำผิดพลาดของการลืมที่จะก่อให้เกิดIDisposable.Dispose()แต่สิ่งที่เราสามารถทำคือการใช้เตาเผาที่จะจับความผิดพลาดนี้ มันเป็นเรื่องง่ายมากจริงๆ: ทุก destructor ที่มีการทำคือการสร้างรายการล็อกหากตรวจพบว่าธงของวัตถุที่ใช้แล้วทิ้งก็ไม่เคยตั้งค่าให้disposed trueดังนั้นการใช้ destructor ไม่ใช่ส่วนหนึ่งของกลยุทธ์การกำจัดของเรา แต่เป็นกลไกการประกันคุณภาพของเรา และเนื่องจากนี่เป็นการทดสอบโหมดดีบักเท่านั้นเราสามารถวาง destructor ทั้งหมดไว้ใน#if DEBUGบล็อกได้ดังนั้นเราจึงไม่เคยได้รับบทลงโทษการทำลายล้างใด ๆ ในสภาพแวดล้อมการผลิต (ในIDisposable.Dispose( bool disposing )สำนวนกำหนดให้GC.SuppressFinalize() ควรเรียกใช้อย่างแม่นยำเพื่อลดค่าใช้จ่ายในการสรุป แต่ด้วยกลไกของฉันเป็นไปได้ที่จะหลีกเลี่ยงค่าใช้จ่ายในสภาพแวดล้อมการผลิตอย่างสมบูรณ์)

สิ่งที่เดือดลงมาคือข้อผิดพลาดที่เป็นนิรันดร์กับข้อผิดพลาดที่อ่อนนุ่ม : IDisposable.Dispose( bool disposing )สำนวนนั้นเป็นวิธีการที่ผิดพลาดอย่างนุ่มนวลและแสดงถึงความพยายามที่จะอนุญาตให้โปรแกรมเมอร์ลืมที่จะเรียกใช้Dispose()โดยไม่เกิดความล้มเหลวของระบบ วิธีข้อผิดพลาดยากบอกว่าโปรแกรมเมอร์จะต้องตรวจสอบให้แน่ใจว่าDispose()จะถูกเรียก การลงโทษมักจะกำหนดโดยวิธีการข้อผิดพลาดที่ยากในกรณีส่วนใหญ่คือความล้มเหลวในการยืนยัน แต่สำหรับกรณีนี้เราทำการยกเว้นและลดการลงโทษไปสู่การออกรายการบันทึกข้อผิดพลาดอย่างง่าย

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


Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()จริงๆแล้วมันไม่ใช่ แต่ฉันไม่คิดว่า C # จะสามารถใช้งานได้ อย่าเปิดเผยทรัพยากร แทนที่จะให้ DSL เพื่ออธิบายทุกสิ่งที่คุณจะทำกับมัน (โดยทั่วไปเป็น monad) รวมถึงฟังก์ชั่นที่ได้มาซึ่งทรัพยากรการทำสิ่งต่าง ๆ ปลดปล่อยและส่งกลับผลลัพธ์ เคล็ดลับคือการใช้ระบบประเภทเพื่อรับประกันว่าถ้ามีคนลักลอบนำออกอ้างอิงถึงทรัพยากรมันไม่สามารถใช้ในการเรียกไปยังฟังก์ชั่นการเรียกใช้อีกครั้ง
Doval

2
ปัญหาที่เกิดขึ้นกับDispose(bool disposing)(ซึ่งไม่ได้กำหนดไว้IDisposableคือมันถูกใช้เพื่อจัดการกับการล้างวัตถุที่ได้รับการจัดการและไม่มีการจัดการวัตถุที่มีเขตข้อมูล (หรือเป็นอย่างอื่นรับผิดชอบ) ซึ่งเป็นการแก้ปัญหาที่ผิดถ้าคุณห่อทั้งหมด วัตถุที่ไม่ได้รับการจัดการในวัตถุที่มีการจัดการโดยไม่มีวัตถุที่ใช้แล้วทิ้งอื่น ๆ ที่ต้องกังวลเกี่ยวกับDispose()วิธีการทั้งหมดจะเป็นหนึ่งในนั้น (ให้ผู้เข้ารอบสุดท้ายทำการล้างข้อมูลเดียวกันหากจำเป็น) หรือมีเพียงวัตถุที่มีการจัดการ เลย) และความจำเป็นในการbool disposingหายตัวไป
Jon Hanna

-1 คำแนะนำที่ไม่ดีเนื่องจากการปิดท้ายใช้งานได้จริง ผมอย่างเต็มที่เห็นด้วยกับจุดของคุณในdispose(disposing)สำนวนเป็น terribad แต่ผมพูดเช่นนี้เพราะผู้คนจึงมักจะใช้เทคนิคและ finalizers เมื่อพวกเขาเท่านั้นที่มีการบริหารจัดการทรัพยากร ( DbConnectionวัตถุตัวอย่างจะมีการจัดการก็ไม่ pinvoked หรือคอม marshalled) และคุณควรเท่านั้น เคยใช้ finalizer กับที่ไม่มีการจัดการ PINVOKED, COM marshalled, หรือรหัสไม่ปลอดภัย ฉันมีรายละเอียดข้างต้นในคำตอบของฉันว่าผู้เข้ารอบสุดท้ายมีราคาแพงเพียงใดอย่าใช้พวกเขาเว้นแต่คุณจะมีทรัพยากรที่ไม่มีการจัดการในชั้นเรียนของคุณ
Jimmy Hoffa

2
ฉันเกือบอยากให้ +1 กับคุณเพราะคุณกำลังพูดอะไรบางอย่างที่หลายคนให้ความสำคัญในdispose(dispoing)สำนวน แต่ความจริงก็คือมันเป็นที่แพร่หลายเพราะคนกลัวสิ่งต่าง ๆ ที่ไม่เกี่ยวข้องกับ GC ที่ ( disposeควรมี zilch จะทำอย่างไรกับ GC) ทำบุญพวกเขาเพียงแค่ใช้ยาที่กำหนดโดยไม่ต้องตรวจสอบมัน ดีสำหรับคุณในการตรวจสอบ แต่คุณพลาดทั้งที่ใหญ่ที่สุด (มันสนับสนุนให้เข้ารอบสุดท้าย farrr บ่อยกว่าที่ควรจะเป็น)
จิมมี่ฮอฟฟา

1
@JimmyHoffa ขอบคุณสำหรับการป้อนข้อมูลของคุณ ฉันยอมรับว่าปกติแล้วเครื่องมือสร้าง finalizer ควรใช้เพื่อปล่อยทรัพยากรที่ไม่มีการจัดการ แต่คุณจะไม่เห็นด้วยหรือไม่ว่าในการสร้าง DEBUG กฎนี้ไม่สามารถใช้ได้และในการสร้าง DEBUG เราควรมีอิสระในการใช้ finalizers เพื่อตรวจจับข้อบกพร่องหรือไม่ นั่นคือทั้งหมดที่ฉันแนะนำที่นี่ดังนั้นฉันไม่เห็นว่าทำไมคุณถึงมีปัญหากับมัน ดูเพิ่มเติมที่programmers.stackexchange.com/questions/288715/…สำหรับคำอธิบายเพิ่มเติมเกี่ยวกับวิธีการนี้ในฝั่งจาวาของโลก
Mike Nakis

0

คุณช่วยบอกฉันได้ไหมว่ามันเป็นความคิดที่ดีหรือสมเหตุสมผลในการจัดเรียงขยะ? ฉันไม่ได้ขอเฉพาะกรณี C # แต่ทุกภาษาโปรแกรมที่มีตัวเก็บขยะ ฉันรู้ว่าคุณไม่สามารถบังคับให้ GC กับทุกภาษาเช่น Java ได้ แต่ลองสมมติว่าคุณทำได้

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

หากคุณดูเกมอินดี้ shoddier บางเกมที่เขียนโดยใช้ภาษา GC เช่นเกม Flash พวกเขาจะรั่วไหลอย่างบ้าคลั่ง แต่ไม่ผิดพลาด พวกเขาอาจใช้เวลาหน่วยความจำสิบนาที 20 นาทีในการเล่นเกมเพราะบางส่วนของ codebase ของเกมลืมที่จะตั้งค่าอ้างอิงเป็นโมฆะหรือลบออกจากรายการและอัตราเฟรมอาจเริ่มประสบ แต่เกมยังคงทำงาน เกมที่คล้ายกันที่เขียนโดยใช้การเข้ารหัสแบบ shoddy C หรือ C ++ อาจมีปัญหาเนื่องจากการเข้าถึงตัวชี้ที่ห้อยต่องแต่งเนื่องจากข้อผิดพลาดในการจัดการทรัพยากรชนิดเดียวกัน แต่มันจะไม่รั่วไหลมากนัก

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

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

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

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