Hone logo
Hone
Problems

Implementing a Simplified Generational Garbage Collector in Go

This challenge asks you to implement a simplified generational garbage collector in Go. Generational GC is a common optimization technique used in many languages to improve garbage collection performance by leveraging the observation that most objects die young. This challenge focuses on the core concepts of generational collection without the complexities of a full production-ready implementation.

Problem Description

You are tasked with creating a simplified generational garbage collector for Go. The collector will manage a pool of objects and periodically perform collections on different generations. The goal is to demonstrate the basic principles of generational GC: dividing memory into generations (young and old), collecting the young generation more frequently, and promoting surviving objects to the old generation.

What needs to be achieved:

  1. Object Allocation: Implement a function to allocate new objects and place them in the young generation.
  2. Generations: Define two generations: a young generation and an old generation. The young generation will be smaller and collected more frequently.
  3. Collection: Implement a Collect function that performs the following:
    • Iterates through the young generation.
    • For each object in the young generation, check if it's still reachable (assume a simple reachability check: if the object's marked field is true, it's reachable; otherwise, it's garbage).
    • If an object is reachable, mark it as marked = false (resetting the mark for the next collection).
    • If an object is unreachable, free its memory (in this simplified version, simply remove it from the young generation).
    • Promote any surviving objects from the young generation to the old generation.
  4. Reachability Marking: Provide a function to mark objects as reachable. This will be used by the application code to indicate that an object is still in use.
  5. Data Structures: Use appropriate data structures (e.g., slices, maps) to represent the generations and the objects within them.

Key Requirements:

  • The collector should manage a fixed-size memory pool for both generations.
  • The Collect function should be called periodically (simulated by the user).
  • The implementation should be clear, concise, and well-documented.
  • The marked field should be part of the object's structure.

Expected Behavior:

  • Objects allocated in the young generation should be collected more frequently than objects in the old generation.
  • Surviving objects from the young generation should be promoted to the old generation.
  • The collector should prevent memory leaks by freeing unreachable objects.

Edge Cases to Consider:

  • What happens when the young generation is full? (For simplicity, you can return an error in this case).
  • What happens when the old generation is full? (For simplicity, you can return an error in this case).
  • How to handle concurrent access to the generations (for simplicity, assume single-threaded access).

Examples

Example 1:

Input: Allocate 10 objects in the young generation, mark 3 as reachable, call Collect.
Output: 7 objects are freed from the young generation. 3 objects are promoted to the old generation.
Explanation: 3 objects are marked as reachable, so they survive. The remaining 7 are unreachable and freed.

Example 2:

Input: Allocate 20 objects in the young generation, mark 5 as reachable, call Collect twice.
Output (First Collect): 15 objects are freed from the young generation. 5 objects are promoted to the old generation.
Output (Second Collect): The old generation is checked, but no objects are freed (assuming no reachability changes).
Explanation: The first collection frees the unreachable objects and promotes the reachable ones. The second collection finds the old generation unchanged.

Example 3: (Edge Case - Young Generation Full)

Input: Young generation is full, attempt to allocate a new object.
Output: Error indicating that the young generation is full.
Explanation: The allocation fails because there's no more space in the young generation.

Constraints

  • Memory Pool Size: Young generation size: 10 objects. Old generation size: 5 objects.
  • Object Size: Assume each object occupies a fixed size of 1 unit of memory (for simplicity).
  • Reachability Check: Objects are reachable if their marked field is true.
  • Performance: Focus on correctness and clarity over extreme performance optimization. The goal is to demonstrate the concept, not to build a production-grade collector.
  • Concurrency: Assume single-threaded access to the collector.

Notes

  • This is a simplified implementation. A real-world GC would be significantly more complex.
  • You don't need to implement a full memory allocator. You can use slices to represent the generations and allocate objects directly into the slices.
  • Focus on the core concepts of generational GC: generation separation, frequent young generation collection, and object promotion.
  • Consider using a struct to represent an object, including fields for data and a marked boolean.
  • Error handling is simplified; return errors when the generations are full.
  • The reachability marking function should be provided by the user of the collector. The collector itself doesn't track object references.
Loading editor...
go