{"id":20293110,"url":"https://github.com/pvande/gc-arena","last_synced_at":"2026-02-26T16:19:11.318Z","repository":{"id":259139572,"uuid":"869299164","full_name":"pvande/gc-arena","owner":"pvande","description":"Opt-in manual memory management for DragonRuby","archived":false,"fork":false,"pushed_at":"2024-10-22T00:02:57.000Z","size":137,"stargazers_count":2,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-14T09:23:02.221Z","etag":null,"topics":["dragonruby","dragonruby-gtk","dragonrubygtk"],"latest_commit_sha":null,"homepage":"https://docs.page/pvande/gc-arena","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pvande.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["pvande"],"ko_fi":"pvande"}},"created_at":"2024-10-08T04:13:07.000Z","updated_at":"2024-12-14T12:41:43.000Z","dependencies_parsed_at":"2024-10-22T23:26:43.257Z","dependency_job_id":null,"html_url":"https://github.com/pvande/gc-arena","commit_stats":null,"previous_names":["pvande/gc-arena"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fgc-arena","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fgc-arena/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fgc-arena/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fgc-arena/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pvande","download_url":"https://codeload.github.com/pvande/gc-arena/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241787482,"owners_count":20020101,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dragonruby","dragonruby-gtk","dragonrubygtk"],"created_at":"2024-11-14T15:21:32.075Z","updated_at":"2026-02-26T16:19:06.296Z","avatar_url":"https://github.com/pvande.png","language":"C","funding_links":["https://github.com/sponsors/pvande","https://ko-fi.com/pvande"],"categories":[],"sub_categories":[],"readme":"# gc-arena\n\nOpt-in manual memory management for DragonRuby.\n\n\u003e [!CAUTION]\n\u003e **This tool is very sharp.**\n\u003e\n\u003e mruby is built with an expectation of automatic memory management. Hidden\n\u003e allocations can happen in unexpected places, and these can be a source of\n\u003e difficult to diagnose bugs. Ruby allows you to remain ignorant about memory\n\u003e details; this tool is not so kind. Please take time to understand how to use\n\u003e this tool first.\n\n## Why This Exists\nGarbage collection is a wonderful feature of many programming languages,\nincluding Ruby, that allows you to foucs on the the logical transformation of\ndata rather than how and where that data is stored. This tool is not without\ncost however, and the cost of the garbage collector provided by mruby *in\nparticular* scales in proportion to the size of the tracked data.\n\nOptimization techniques like preprocessing, memoization, indexing, and caching\n— usually viewed as a trading memory for speed — end up being less effective\nbecause of the increased cost of garbage collection. Large volumes of completely\nstatic data end up being tested for liveness repeatedly. While changes\nintroduced in DragonRuby 6.2 have significantly redcuced the effective cost of\ngarbage collection, a full GC cycle can still introduce a noticeable periodic\ndelay or a significant delay at startup.\n\n`gc-arena` provides memory pools that the GC _fully ignores_, giving you space\nto store as much data as you want, at no additional cost, and to choose if and\nwhen that data should be freed. Used wisely, this can be a form of manual memory\nmanagement with many of the same benefits as garbage collection, without the any\nof the drawbacks.\n\n## How It Works\nWhen a new `GC::Arena` is created, it allocates a pool of memory, which will be\nbound to that instance for its entire lifetime. `GC::Arena#eval` temporarily\nswaps out the object heap and allocator used by mruby to utilize the Arena's\nmemory pool. All newly created objects — and any other allocations, like long\nstring storage — will live in the Arena's pool and are flagged so that they are\nnot traversed by the GC.\n\n## When To Use It\nArena allocation can be a great choice when the cost of garbage collection\nbecomes noticeable, or when the cost of memory allocation is a concern.\n\nConsider using an arena for:\n* Large data caches that live for the entire runtime of the game.\n* Scene data, like level geometry and objects.\n* Per-frame data and intermediate calculations.\n\nMore generally, they can be useful whenever you have 1) some amount of 2) data\nwith a known lifetime. [Untangling Lifetimes] is a great resource for thinking\nabout how arenas work and why they are useful, from the perspective of a C\nprogrammer.\n\n## How To Use It\n\n``` ruby\n# Load the library.\ndef boot(...)\n  $gtk.dlopen(\"gc-arena\")\nend\n\n# Create a new Arena.\n# Parameters allow you to specify how many object slots and how much additional\n# memory should be preallocated to this arena, which can help reduce runtime\n# allocation costs. (Additional memory is measured in bytes.)\narena = GC::Arena.allocate(objects: 1024, memory: 1024)\n\n# Objects created outside the Arena are subject to normal garbage collection.\nvar = Garbage.new\nvar = nil # The GC will eventually reclaim the `Garbage` instance.\n\n# Using `Arena#eval` will temporarily swap contexts.\narena.eval do\n  # Objects created inside the Arena live within the Arena's memory pool.\n  # They are not subject to garbage collection.\n  var = Object.new\nend\n\n# `Arena#eval` also returns the value of the last expression.\nmy_object = arena.eval { Object.new }\n\n# Unlike normal, garbage collected objects, references to Arena allocated\n# objects will become invalid (causing errors and possibly crashes) when the\n# Arena's memory is freed. This will happen automatically when the Arena is\n# garbage collected.\narena = nil\n```\n\n## What Not To Do\n\n### Use After Free\n\u003e [!IMPORTANT]\n\u003e  Rule: **Object references must not outlive their Arena.**\n\nArenas will free their associated memory when garbage collected, or when\nexplicitly asked to do so. This can lead to situations, unlike in typical Ruby\ncode, where an object's memory might be released while there are still\nreferences to that memory location. Attempting to read or write from freed\nmemory is generally unsafe, may not fail *immediately*, and can result in\nvariables \"suddenly\" having unexpected new values, or application crashes.\n\n\u003e 🚫 Incorrect:\n\u003e\n\u003e ``` ruby\n\u003e def make_object\n\u003e   # @NOTE This arena will be freed when this function returns.\n\u003e   arena = GC::Arena.allocate(objects: 1)\n\u003e   arena.eval { Object.new }\n\u003e end\n\u003e\n\u003e obj = make_object # @ERROR `obj` now refers to freed memory.\n\u003e```\n\n\u003e ✅ Correct:\n\u003e\n\u003e ``` ruby\n\u003e $arena = GC::Arena.allocate(objects: 1)\n\u003e\n\u003e def make_object\n\u003e   $arena.eval { Object.new }\n\u003e end\n\u003e\n\u003e obj = make_object # @NOTE `obj` refers to memory owned by `$arena`.\n\u003e ```\n\n---\n\n\u003e [!IMPORTANT]\n\u003e  Rule: **Arena objects must not reference objects with shorter lifetimes.**\n\nThis is functionally equivalent to the previous rule, but it's worth calling out\nseparately. Variables are not the only object references that can become invalid\n— instance variables, hash keys and values, and array entries all form\nreferences that *could* become the source of a use-after-free error.\n\nObject references become invalid when the object is freed. For arena-allocated\nobjects, this will happen when the owning Arena is freed; for GC-allocated\nobjects, this will happen when the GC cannot find any live references to the\nobject. Because Arena-allocated objects are not traversed by the garbage\ncollector, they cannot form a \"live reference\" for the GC.\n\n\u003e 🚫 Incorrect:\n\u003e\n\u003e ``` ruby\n\u003e $arena = GC::Arena.allocate(objects: 1)\n\u003e list = $arena.eval { [] }\n\u003e\n\u003e begin\n\u003e   # @NOTE This arena will be freed when this block ends.\n\u003e   $temp = GC::Arena.allocate(objects: 1)\n\u003e   list \u003c\u003c $temp.eval { Object.new }\n\u003e end\n\u003e\n\u003e # @ERROR `list` now contains a reference to freed memory.\n\u003e```\n\n\u003e 🚫 Incorrect:\n\u003e\n\u003e ``` ruby\n\u003e $arena = GC::Arena.allocate(objects: 1)\n\u003e list = $arena.eval { [] }\n\u003e\n\u003e def add_point(list)\n\u003e   # @NOTE This hash is GC-allocated, and will be freed after this method call.\n\u003e   list \u003c\u003c { x: 0, y: 0 }\n\u003e end\n\u003e\n\u003e add_point(list) # @ERROR `list` now contains a reference to freed memory.\n\u003e```\n\n\u003e ✅ Correct:\n\u003e\n\u003e ``` ruby\n\u003e $arena = GC::Arena.allocate(objects: 1)\n\u003e list = $arena.eval { [] }\n\u003e\n\u003e def add_point(list)\n\u003e   list \u003c\u003c $arena.eval { { x: 0, y: 0 } }\n\u003e end\n\u003e\n\u003e add_point(list) # @NOTE `list` only contains references owned by `$arena`.\n\u003e ```\n\n### Resource Retention\n\u003e [!IMPORTANT]\n\u003e  Rule: **Objects bound to non-memory resources must live in the regular GC.**\n\nSome types of objects — particularly objects from other C extensions — will have\nteardown routines that are called when the object is garbage collected. Since\nArena-allocated objects aren't ever garbage collected, those routines are never\nrun. In cases where that behavior is more involved than just freeing allocated\nmemory, those objects should not be allocated inside an Arena, or retained by an\nobject allocated inside an Arena.\n\nThis will primarily be objects that act as a handle to an OS resource, like a\nsocket or a file descriptor. Creating these inside an Arena will cause the\nobject's memory to be freed, but the resource will remain bound until the\nprocess terminates.\n\n\u003e 🚫 Incorrect:\n\u003e\n\u003e ``` ruby\n\u003e $arena = GC::Arena.allocate(objects: 1)\n\u003e $arena.eval do\n\u003e   # @ERROR `$stream` will never be properly cleaned up.\n\u003e   $stream = IO.popen(\"curl https://example.com/streaming-data-source\")\n\u003e end\n\u003e ```\n\n\u003e ✅ Correct:\n\u003e\n\u003e ``` ruby\n\u003e # There is no correct way to reference these objects within an Arena.\n\u003e ```\n\n[Untangling Lifetimes]:\n    https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpvande%2Fgc-arena","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpvande%2Fgc-arena","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpvande%2Fgc-arena/lists"}