การรวบรวมขยะทำงานในภาษาที่เรียบเรียงอย่างไร


79

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

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

สิ่งนี้จะทำงานกับภาษาที่คอมไพล์ได้อย่างไร? ความเข้าใจของฉันคือเมื่อคอมไพเลอร์ได้รวบรวมซอร์สโค้ดไปยังรหัสเป้าหมาย - โดยเฉพาะรหัสเครื่องดั้งเดิม - ก็เสร็จแล้ว งานเสร็จสิ้นแล้ว ดังนั้นโปรแกรมที่คอมไพล์แล้วจะเก็บขยะได้อย่างไร?

คอมไพเลอร์ทำงานร่วมกับ CPU ในบางวิธีในขณะที่โปรแกรมทำงานเพื่อลบวัตถุ "ขยะ" หรือไม่ หรือคอมไพเลอร์มีตัวรวบรวมขยะน้อยที่สุดในการปฏิบัติการของโปรแกรมที่คอมไพล์แล้วหรือไม่

ฉันเชื่อว่าคำสั่งหลังของฉันจะมีความถูกต้องมากกว่าเดิมเนื่องจากข้อความที่ตัดตอนมาจากคำตอบนี้ใน Stack Overflow :

ภาษาโปรแกรมหนึ่งนั้นคือไอเฟล คอมไพเลอร์ไอเฟลส่วนใหญ่สร้างรหัส C สำหรับเหตุผลในการพกพา รหัส C นี้ใช้เพื่อสร้างรหัสเครื่องโดยคอมไพเลอร์ C มาตรฐาน Eiffel implementations ให้ GC (และบางครั้งก็แม่นยำ GC) สำหรับโค้ดที่คอมไพล์นี้และไม่จำเป็นสำหรับ VM โดยเฉพาะอย่างยิ่ง VisualEiffel คอมไพเลอร์สร้างรหัสเครื่อง x86 พื้นเมืองโดยตรงกับการสนับสนุน

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

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

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

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

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


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

6
ถ้าคุณยอมรับความจริงที่ว่า GC เป็นพื้นส่วนหนึ่งของห้องสมุดจำเป็นโดยการดำเนินการเขียนโปรแกรมภาษาโดยเฉพาะอย่างยิ่งแล้วสรุปสาระสำคัญของคำถามของคุณมีอะไรจะทำอย่างไรกับ GC ต่อ se และทุกอย่างจะทำอย่างไรกับแบบคงที่เมื่อเทียบกับการเชื่อมโยงแบบไดนามิก
Theodoros Chatzigiannakis

7
คุณสามารถพิจารณาตัวรวบรวมขยะให้เป็นส่วนหนึ่งของไลบรารีรันไทม์ที่ใช้ภาษาที่เทียบเท่าmalloc()ได้
Barmar

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

1
GC เป็นคุณสมบัติของหน่วยความจำแบบไดนามิกไม่ใช่คุณสมบัติของล่าม
Dmitry Grigoryev

คำตอบ:


52

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

(การติดตาม) การรวบรวมขยะมักจะเริ่มต้นด้วยการเดินกองการโทรของเธรดทั้งหมดที่กำลังทำงานอยู่ วัตถุบนกองเหล่านั้นจะมีชีวิตอยู่เสมอ หลังจากนั้นตัวรวบรวมขยะจะสำรวจวัตถุทั้งหมดที่ชี้ไปตามวัตถุที่มีชีวิตจนกว่าจะพบกราฟวัตถุที่มีชีวิตทั้งหมด

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

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

ตัวรวบรวมขยะเองไม่มีอะไรมากไปกว่าไลบรารี่ที่เชื่อมโยงกับโปรแกรมซึ่งคล้ายกับไลบรารี่มาตรฐาน C ตัวอย่างเช่นไลบรารีนี้สามารถจัดเตรียมฟังก์ชันที่คล้ายกับmalloc()ที่เรียกใช้อัลกอริธึมการรวบรวมหากความดันหน่วยความจำสูง


9
ระหว่างยูทิลิตี้ไลบรารีและการคอมไพล์ JIT เส้นระหว่าง "คอมไพล์กับเนทีฟ" และ "รันในสภาวะแวดล้อมรันไทม์" กำลังเบลอมากขึ้นเรื่อย ๆ
corsiKa

6
เพียงเพิ่มบิตเกี่ยวกับภาษาที่ไม่ได้รับการสนับสนุนจาก GC: มันเป็นความจริงที่ C และภาษาอื่น ๆ ไม่ได้ให้ข้อมูลเกี่ยวกับสแต็คการโทร แต่ถ้าคุณตกลงกับรหัสเฉพาะแพลตฟอร์มบางตัว ของแอสเซมบลีรหัส) ยังคงเป็นไปได้ที่จะใช้ "การเก็บขยะแบบอนุรักษ์นิยม" Boehm GCเป็นเช่นนี้มาใช้ในโปรแกรมชีวิตจริง
Matti Virkkunen

2
@corsiKa หรือว่าบรรทัดนั้นชัดเจนกว่านี้มาก ตอนนี้เราเห็นแล้วว่ามันเป็นแนวคิดที่ไม่เกี่ยวข้องกันและไม่ใช่คำตรงกันข้ามของแต่ละคน
Kroltan

4
ความซับซ้อนเพิ่มเติมอย่างหนึ่งที่คุณต้องระวังในการแปล runtimes ที่แปลแล้วแปลนั้นเกี่ยวข้องกับประโยคนี้ในคำตอบของคุณ: "การติดตาม (ขยะ) โดยปกติแล้วการรวบรวมขยะจะเริ่มต้นด้วยการเดินเรียกกองซ้อนของเธรดทั้งหมดที่กำลังทำงานอยู่" ประสบการณ์ของฉันในการติดตั้ง GC ในสภาพแวดล้อมที่คอมไพล์คือการติดตามสแต็กไม่เพียงพอ จุดเริ่มต้นมักจะระงับเธรดเป็นเวลานานพอที่จะติดตามจากการลงทะเบียนของพวกเขาเพราะพวกเขาอาจมีการอ้างอิงในการลงทะเบียนเหล่านั้นที่ยังไม่ได้ถูกเก็บไว้ในกอง สำหรับล่ามมันมักจะไม่ ...
Jules

... ปัญหาเนื่องจากสภาพแวดล้อมสามารถจัดการให้ GC เกิดขึ้นที่ "safe points" ซึ่งล่ามรู้ว่าข้อมูลทั้งหมดจะถูกเก็บไว้อย่างปลอดภัยในสแต็คที่ถูกตีความ
Jules

123

คอมไพเลอร์เก็บสำเนาของโปรแกรมรวบรวมขยะบางส่วนและวางลงในไฟล์เรียกทำงานที่สร้างขึ้นหรือไม่

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


51
@ChristianDean โปรดทราบว่าแม้ C มีไลบรารีรันไทม์ แม้ว่าจะไม่มี GC แต่ก็ยังทำการจัดการหน่วยความจำผ่านทางรันไทม์ไลบรารี่นั้นmalloc()และfree()ไม่ได้มีอยู่ในภาษาไม่ใช่ส่วนหนึ่งของระบบปฏิบัติการ แต่เป็นฟังก์ชั่นในไลบรารีนี้ C ++ บางครั้งก็ถูกคอมไพล์ด้วยไลบรารีคอลเล็กชันขยะแม้ว่าภาษานั้นไม่ได้ออกแบบโดยคำนึงถึง GC
amon

18
C ++ ยังมีไลบรารีรันไทม์ที่ทำสิ่งต่าง ๆ เช่น make dynamic_castและ exception ยกเว้นแม้ว่าคุณจะไม่ได้เพิ่ม GC
เซบาสเตียนเรดล

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

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

15
@millimoose: ใช่ ตัวอย่างเช่นใน GCC โค้ดนี้คือcrt0.o(ซึ่งย่อมาจาก " C R un T ime, พื้นฐานเบื้องต้น") ซึ่งได้รับการเชื่อมโยงกับทุกโปรแกรม (หรืออย่างน้อยทุกโปรแกรมที่ไม่ได้เป็นอิสระ )
Jörg W Mittag

58

หรือคอมไพเลอร์มีตัวรวบรวมขยะน้อยที่สุดในโค้ดของโปรแกรมที่คอมไพล์แล้วหรือไม่

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

นี้จะไม่มีอะไรพิเศษ: คอมไพเลอร์มักจะเชื่อมโยงตันของห้องสมุดในโปรแกรมที่พวกเขารวบรวม; มิฉะนั้นโปรแกรมที่คอมไพล์แล้วจะทำอะไรไม่ได้เลยหากไม่นำสิ่งต่าง ๆ ไปใช้ใหม่ตั้งแต่ต้น: แม้แต่การเขียนข้อความลงบนหน้าจอ / ไฟล์ / ... ต้องมีไลบรารี

แต่ GC อาจแตกต่างจากไลบรารีอื่น ๆ เหล่านี้ซึ่งมี API ที่ชัดเจนที่ผู้ใช้เรียกใช้

ไม่: ในภาษาส่วนใหญ่ไลบรารีรันไทม์ทำงานเบื้องหลังหลายอย่างโดยไม่ต้องใช้ API สาธารณะนอกเหนือจาก GC ลองพิจารณาตัวอย่างทั้งสามนี้

  1. ข้อยกเว้นการแพร่กระจายและการเรียกสแต็ก uninding / destructor
  2. การจัดสรรหน่วยความจำแบบไดนามิก (ซึ่งมักจะไม่เพียงเรียกฟังก์ชั่นเช่นเดียวกับใน C แม้ว่าจะไม่มีการรวบรวมขยะ)
  3. การติดตามข้อมูลประเภทไดนามิก (สำหรับการปลดเปลื้อง ฯลฯ )

ดังนั้นไลบรารีคอลเลกชันขยะจึงไม่พิเศษเลยและนิรนัยก็ไม่มีส่วนเกี่ยวข้องกับการรวบรวมโปรแกรมล่วงหน้า


สิ่งนี้ดูเหมือนจะไม่นำเสนอสิ่งใดเกินกว่าที่ทำและอธิบายไว้ในคำตอบยอดนิยมที่โพสต์เมื่อ 3 ชั่วโมงก่อน
gnat

11
@gnat ฉันรู้สึกว่ามันมีประโยชน์ / จำเป็นเพราะคำตอบยอดเยี่ยมยังไม่แข็งแรงพอ: มันพูดถึงข้อเท็จจริงที่คล้ายกัน โดยพื้นฐานแล้วสมมติฐานของ OP มีข้อบกพร่องและคำตอบยอดนิยมไม่ได้กล่าวถึงสิ่งนี้ ฉันทำ (ในขณะที่หลีกเลี่ยงคำที่ค่อนข้างรุนแรง "มีข้อบกพร่อง")
Konrad Rudolph

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

7
ไลบรารี @millimoose Runtime ทำงานอยู่เบื้องหลังด้วยหลากหลายวิธีโดยไม่มีการโต้ตอบกับผู้ใช้อย่างชัดเจน พิจารณาข้อยกเว้นการแพร่กระจายและการโทรสแต็ก uninding / destructor พิจารณาการจัดสรรหน่วยความจำแบบไดนามิก (ซึ่งมักจะไม่เพียงเรียกฟังก์ชั่นเช่นเดียวกับใน C แม้ในขณะที่ไม่มีการเก็บขยะ) พิจารณาการจัดการข้อมูลประเภทไดนามิก (สำหรับการปลดเปลื้อง ฯลฯ ) ดังนั้น GC จึงไม่ซ้ำกันจริงๆ
Konrad Rudolph

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

23

มันจะทำงานกับภาษาที่คอมไพล์ได้อย่างไร?

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

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

ความเข้าใจของฉันคือเมื่อคอมไพเลอร์ได้รวบรวมซอร์สโค้ดไปยังรหัสเป้าหมาย - โดยเฉพาะรหัสเครื่องดั้งเดิม - ก็เสร็จแล้ว งานเสร็จสิ้นแล้ว

ขอให้สังเกตว่าล่ามและคอมไพเลอร์มีความหมายหลวมและการใช้งานภาษาบางอย่างอาจถือได้ว่าเป็นทั้งสองอย่าง กล่าวอีกนัยหนึ่งมีความต่อเนื่องในระหว่าง อ่านDragon Bookล่าสุดและคิดเกี่ยวกับbytecode , การรวบรวม JIT , ปล่อยรหัส C แบบไดนามิกที่รวบรวมเป็น "plugin" บางส่วนจากนั้นdlopen (3) -ed โดยกระบวนการเดียวกัน (และบนเครื่องปัจจุบันนี้เร็วพอที่จะเข้ากันได้กับREPLเชิงโต้ตอบดูสิ่งนี้ )


ผมขอแนะนำให้อ่านคู่มือ GC หนังสือทั้งเล่มเป็นสิ่งจำเป็นที่จะตอบ ก่อนหน้านั้นอ่านwikipage Garbage Collection (ซึ่งฉันคิดว่าคุณได้อ่านก่อนที่จะอ่านด้านล่าง)

ระบบรันไทม์ของการใช้ภาษาเรียบเรียงมีการเก็บขยะและคอมไพเลอร์จะสร้างรหัสซึ่งเป็นพอดีกับระบบรันไทม์ที่เฉพาะเจาะจง โดยเฉพาะอย่างยิ่งการปันส่วนพื้นฐาน (ถูกคอมไพล์ไปยังรหัสเครื่องที่) (หรืออาจ) เรียกระบบรันไทม์

ดังนั้นโปรแกรมที่คอมไพล์แล้วจะเก็บขยะได้อย่างไร?

เพียงแค่เปล่งรหัสเครื่องซึ่งใช้ (และ "เป็นมิตร" และ "เข้ากันได้กับ") ระบบรันไทม์

โปรดสังเกตว่าคุณสามารถค้นหาห้องสมุดเก็บขยะได้หลายแห่งโดยเฉพาะBoehm GC , MPS ของ Ravenbrookหรือแม้แต่Qish ของฉัน และการเข้ารหัสGC อย่างง่ายนั้นไม่ยากมาก (อย่างไรก็ตามการดีบั๊กมันยากขึ้นและการเข้ารหัสGC ที่แข่งขันยากนั้น)

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

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

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

คอมไพเลอร์เก็บสำเนาของโปรแกรมรวบรวมขยะบางส่วนและวางลงในแต่ละไฟล์ที่สร้างขึ้นได้หรือไม่?

เรียงลำดับของ อย่างไรก็ตามระบบรันไทม์อาจจะเป็นห้องสมุดที่ใช้ร่วมกัน ฯลฯ บางครั้ง (บน Linux และระบบหลาย POSIX อื่น ๆ ) มันก็อาจจะเป็นล่ามสคริปต์เช่นส่งผ่านไปยังexecve (2)มีshebang หรือล่ามเอลฟ์ดูเอลฟ์ (5)และPT_INTERPอื่น ๆ

BTW คอมไพเลอร์มากที่สุดสำหรับภาษาที่มีการเก็บขยะ (และระบบรันไทม์ของพวกเขา) มีวันนี้ซอฟต์แวร์ฟรี ดังนั้นดาวน์โหลดซอร์สโค้ดและศึกษามัน


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

4
@ KonradRudolph: ขึ้นอยู่กับคำจำกัดความของคุณว่า "เป็นทางการ" และ "ข้อมูลจำเพาะ" :-D มีISO / IEC 30170: 2012 Ruby Programming Language Specificationซึ่งระบุเซตย่อยเล็ก ๆ ของการแยกของ Ruby 1.8 และ 1.9 มีRuby Spec Suiteซึ่งเป็นตัวอย่างของกรณีขอบเขตที่ทำหน้าที่เป็น "executable spec" จากนั้นทับทิมเขียนโปรแกรมภาษาโดยเดวิดฟลานาแกนและยูกิฮิโระมัตสึ
Jörg W Mittag

4
นอกจากนี้เอกสารทับทิม การอภิปรายในประเด็นเกี่ยวกับทับทิมติดตามปัญหา การสนทนาเกี่ยวกับรายชื่อผู้รับจดหมาย ruby-core (อังกฤษ) และ ruby-dev (ญี่ปุ่น) ความคาดหวังสามัญสำนึกของชุมชน (เช่นArray#[]O (1) กรณีที่เลวร้ายที่สุดHash#[]คือ O (1) กรณีที่เลวร้ายที่สุดตัดจำหน่าย) และสุดท้าย แต่ไม่ท้ายสุดสมองของ matz
Jörg W Mittag

6
@ KonradRudolph: ประเด็นคือ: แม้กระทั่งภาษาที่ไม่มีสเปคอย่างเป็นทางการและมีเพียงการใช้งานเพียงครั้งเดียวยังสามารถแยกออกเป็น "ภาษา" (กฎนามธรรมและข้อ จำกัด ) และ "การดำเนินการ" (โปรแกรมประมวลผลรหัสตามกฎเหล่านั้นและ ข้อ จำกัด). และการนำไปปฏิบัติยังคงทำให้เกิดสเปคแม้ว่าจะเป็นเรื่องเล็กน้อยกล่าวคือ: "สิ่งที่โค้ดทำคือสเป็ค" นั่นคือวิธีที่ข้อมูลจำเพาะ ISO, RubySpec และ RDocs ถูกเขียนขึ้นมาหลังจากทั้งหมด: โดยการเล่นกับวิศวกรรม MRI ที่มีและ / หรือย้อนกลับ MRI
Jörg W Mittag

1
ดีใจที่คุณเลี้ยงถังขยะของ Bohem ขึ้น ฉันอยากจะแนะนำให้ศึกษา OP เพราะมันเป็นตัวอย่างที่ยอดเยี่ยมของวิธีการเก็บรวบรวมขยะแบบง่าย ๆ แม้จะ "ยึดติด" กับคอมไพเลอร์ที่มีอยู่
Cort Ammon

6

มีคำตอบที่ดีอยู่แล้ว แต่ฉันต้องการลบความเข้าใจผิดบางประการเกี่ยวกับคำถามนี้

ไม่มีสิ่งเช่น "ภาษาที่รวบรวมโดยกำเนิด" ต่อ se ตัวอย่างเช่นรหัส Java เดียวกันถูกตีความ (และจากนั้นเพียงบางส่วนรวบรวมเวลารันไทม์) บนโทรศัพท์เครื่องเก่าของฉัน (Java Dalvik) และถูกรวบรวมไว้ล่วงหน้าบนโทรศัพท์เครื่องใหม่ของฉัน (ART)

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

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

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

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

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


(*) อย่างน้อยเมื่อพูดถึงรหัสวัตถุประสงค์ทั่วไปให้ทิ้ง boot boot และสิ่งที่คล้ายกัน


ทั้ง Ocaml และ SBCL เป็นคอมไพเลอร์ดั้งเดิม ดังนั้นจึงมีเป็น "ภาษาเรียบเรียงโดยกำเนิด" การใช้งาน
Basile Starynkevitch เมื่อ

@BasileStarynkevitch WAT? การตั้งชื่อคอมไพเลอร์ที่มีชื่อเสียงน้อยกว่าเกี่ยวข้องกับคำตอบของฉันอย่างไร SBCL ในฐานะคอมไพเลอร์สำหรับภาษาที่ตีความในตอนแรกไม่ใช่ข้อโต้แย้งที่สนับสนุนข้อกล่าวอ้างของฉันว่าความแตกต่างไม่สมเหตุสมผล
maaartinus

Common Lisp (หรือภาษาอื่น ๆ ) ไม่ได้แปลหรือเรียบเรียง มันเป็นภาษาการเขียนโปรแกรม (สเปค) การนำไปปฏิบัติสามารถเป็นคอมไพเลอร์หรือล่ามหรือบางสิ่งในระหว่าง (เช่นล่าม bytecode) SBCL เป็นการใช้งานคอมไพล์แบบอินเตอร์แอคทีฟของ Common LISP Ocaml ยังเป็นภาษาการเขียนโปรแกรม (ทั้งตัวแปล bytecode และตัวแปลภาษาเป็นการนำไปใช้)
Basile Starynkevitch เมื่อ

@BasileStarynkevitch นั่นคือสิ่งที่ฉันอ้างสิทธิ์ 1. ไม่มีสิ่งใดที่แปลหรือเรียบเรียงเป็นภาษา (แม้ว่า C จะถูกตีความไม่ค่อยบ่อยนักและ LISP เคยถูกแปลค่อนข้างบ่อย แต่สิ่งนี้ไม่สำคัญเลย) 2. มีการนำไปใช้แปลตีความรวบรวมและผสมสำหรับภาษาที่รู้จักกันดีที่สุดและไม่มีภาษาห้ามการรวบรวมหรือตีความ
maaartinus

6
ฉันคิดว่าเหตุผลของคุณสมเหตุสมผลแล้ว จุดสำคัญในการ grok คือคุณมักจะเรียกใช้ "โปรแกรมพื้นเมือง" หรือ "ไม่เคย" แต่คุณต้องการที่จะเห็นมัน ไม่มี exe บน Windows ที่สามารถใช้งานได้ มันต้องการตัวโหลดและคุณสมบัติอื่น ๆ ของระบบปฏิบัติการที่จะเริ่มต้นและส่วนหนึ่งก็เป็น "ตีความ" เช่นกัน สิ่งนี้เห็นได้ชัดเจนยิ่งขึ้นเมื่อใช้. executables สุทธิ java myprogมีมากหรือน้อยพื้นเมืองgrep myname /etc/passwdหรือเป็นld.so myprog: มันเป็นปฏิบัติการ (สิ่งที่หมายถึง) ซึ่งจะโต้แย้งและดำเนินการกับข้อมูล
Peter A. Schneider

3

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

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

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

อัลกอริทึม GC ได้รับการศึกษานับตั้งแต่ยุค 60 และมีวรรณกรรมมากมายเกี่ยวกับเรื่องนี้ Google ถ้าคุณต้องการข้อมูลเพิ่มเติม

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