{"id":13670619,"url":"https://github.com/eapache/wof_alloc","last_synced_at":"2025-03-22T05:31:12.976Z","repository":{"id":148872542,"uuid":"12651112","full_name":"eapache/wof_alloc","owner":"eapache","description":"Fast C/C++ memory allocator with free-all operation","archived":false,"fork":false,"pushed_at":"2015-07-17T03:24:38.000Z","size":216,"stargazers_count":55,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-18T08:48:11.300Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eapache.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-09-06T18:15:01.000Z","updated_at":"2025-01-15T03:46:05.000Z","dependencies_parsed_at":"2023-04-01T02:47:55.580Z","dependency_job_id":null,"html_url":"https://github.com/eapache/wof_alloc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eapache%2Fwof_alloc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eapache%2Fwof_alloc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eapache%2Fwof_alloc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eapache%2Fwof_alloc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eapache","download_url":"https://codeload.github.com/eapache/wof_alloc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244912800,"owners_count":20530764,"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":[],"created_at":"2024-08-02T09:00:46.419Z","updated_at":"2025-03-22T05:31:11.565Z","avatar_url":"https://github.com/eapache.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"Wheel-of-Fortune Memory Allocator\n=================================\n\nA little while ago, the [Wireshark Project](https://www.wireshark.org/) was in\nneed of a new memory management framework to replace its aging 'emem' framework.\nNothing available seemed to suit, so I (mostly) wrote one from scratch, called\n[wmem](https://code.wireshark.org/review/gitweb?p=wireshark.git;a=blob;f=doc/README.wmem).\n\nThe majority of wmem was bog-standard memory-pool code, but there was one\ncomponent that ended up turning into a *very* interesting exercise in\nalgorithms and data structures. Within wmem it is simply known as the \"block\"\nallocator, but it was interesting enough that I extracted it from Wireshark,\nand made it available stand-alone here. The wheel-of-fortune name will become\nobvious once you understand how the recycler structure works.\n\nIt is one C source file that uses nothing but bare-bones standard C90, so it\nshould compile on basically any platform in existence.\n\nBehaviour\n---------\n\nFor a proper understanding of how the allocator works, read the rest of this\nREADME. In brief though, `wof_alloc` is constant-time unless it needs to grab a\nnew block from the OS, the cost of which can be amortized. `wof_free` is always\nconstant-time. `wof_realloc` is constant-time if it doesn't have to move the\nblock, and is of course just a `wof_alloc`, `memcpy`, `wof_free` if it does has to move the\nblock.\n\n`wof_free_all` is *very* fast - the design was optimized for this operation,\nwhich in most allocators is either unavailable or quite slow. This operation\ndoes not actually return any memory to the OS, permitting the pool to be\nefficiently reused. `wof_gc` (also very fast) will return what memory it can to\nthe OS (so a `wof_free_all` followed by a `wof_gc` will actually return all\nmemory to the OS, with the exception of the pool struct itself).\n`wof_allocator_destroy` does this automatically, so you don't have to worry\nabout leaking when destroying a pool.\n\nFragmentation depends heavily on the allocation patterns. It behaves quite well\n(almost on par with more traditional allocators) under the common load of\nfixed-size, short-lived allocs, and very few reallocs. However, it can perform\nas much as 3x worse under many variable-sized reallocs. The best way to tell how\nit will perform for you is, of course, to try it.\n\nIn Wireshark, a pool is created, and used for any allocations needed during the\ndissection of a packet, most of which are not explicitly freed even when they\npass out of scope. When the packet has been dissected, `free_all` is called and\nthe same pool is reused for the next packet to arrive.\n\nHistory\n-------\n\nVersion 1 of this allocator was embedded in Wireshark's original emem framework.\nIt didn't have to handle realloc or free, so it was very simple: it just grabbed\na block from the OS and served allocations sequentially out of that until it\nran out, then allocated a new block. The old block was never revisited, so\nit generally had a bit of wasted space at the end, but the waste was\nsmall enough that it was simply ignored. This allocator provided very fast\nconstant-time allocation for any request that didn't require a new block from\nthe OS, and that cost could be amortized away.\n\nVersion 2 of this allocator was prompted by the need to support realloc and\nfree in wmem. The original version simply didn't save enough metadata to do\nthis, so I added a layer on top to make it possible. The primary principle\nwas the same (allocate sequentially out of big blocks) with a bit of extra\nmagic. Allocations were still fast constant-time, and frees were as well.\nSome parts of that design are still present in this one. For more\ndetails see older versions of the wmem block allocator from Wireshark's\nsubversion repository.\n\nVersion 3 of this allocator was written to address some issues that\neventually showed up with version 2 under real-world usage. Specifically,\nversion 2 dealt very poorly with memory fragmentation, almost never reusing\nfreed blocks and choosing to just keep allocating from the master block\ninstead. This led to particularly poor behaviour under certain allocation\npatterns that showed up a lot in Wireshark.\n\nBlocks and Chunks\n-----------------\n\nAs in previous versions, allocations typically happen sequentially out of\nlarge OS-level blocks. Each block has a short embedded header used to\nmaintain a doubly-linked list of all blocks (used or not) currently owned by\nthe allocator. Each block is divided into chunks, which represent allocations\nand free sections (a block is initialized with one large, free, chunk). Each\nchunk is prefixed with a `wof_chunk_hdr_t` structure, which is a short\nmetadata header (typically 8 bytes, depending on architecture and alignment\nrequirements) that contains the length of the chunk, the length of the previous\nchunk, a flag marking the chunk as free or used, and a flag marking the last\nchunk in a block. This serves to implement an inline sequential doubly-linked\nlist of all the chunks in each block. A block with three chunks might look\nsomething like this:\n\n```\n         0                    _________________________\n         ^      ___________  /        ______________   \\       __________\n||---||--|-----/-----------||--------/--------------||--\\-----/----------||\n||hdr|| prv | len |  body  || prv | len |   body    || prv | len | body  ||\n||---||--------------------||--/--------------------||-------------------||\n       \\______________________/\n```\n\nWhen allocating, a free chunk is found (more on that later) and split into\ntwo chunks: the first of the requested size and the second containing any\nremaining memory. The first is marked used and returned to the caller.\n\nWhen freeing, the chunk in question is marked as free. Its neighbouring\nchunks are then checked; if either of them are free, the consecutive free\nchunks are merged into a single larger free chunk. Induction can show that\napplying this operation consistently prevents us ever having consecutive\nfree chunks.\n\nFree chunks (because they are not being used for anything else) each store an\nadditional pair of pointers (see the `wof_free_hdr_t` structure) that form\nthe backbone of the data structures used to track free chunks.\n\nMaster and Recycler\n-------------------\n\nThe extra pair of pointers in free chunks are used to build two doubly-linked\nlists: the master and the recycler. The recycler is circular, the master is\na stack.\n\nThe master stack is only populated by chunks from new OS-level blocks,\nso every chunk in this list is guaranteed to be able to serve any allocation\nrequest (the allocator has special 'jumbo' logic to handle requests larger than\nits block size). The chunk at the head of the master list shrinks as it serves\nrequests. When it is too small to serve the current request, it is popped and\ninserted into the recycler. If the master list is empty, a new OS-level block\nis allocated, and its chunk is pushed onto the master stack.\n\nThe recycler is populated by 'leftovers' from the master, as well as any\nchunks that were returned to the allocator via a call to `wof_free()`. Although\nthe recycler is circular, we will refer to the element referenced from the\nallocator as the 'head' of the list for convenience. The primary operation on\nthe recycler is called cycling it. In this operation, the head is compared\nwith its clockwise neighbour. If the neighbour is as large or larger, it\nbecomes the head (the list rotates counter-clockwise). If the neighbour is\nsmaller, then it is removed from its location and inserted as the counter-\nclockwise neighbour of the head (the list still rotates counter-clockwise,\nbut the head element is held fixed while the rest of the list spins). This\noperation has the following properties:\n - fast constant time\n - once the largest chunk is at the head, it remains at the head\n - more iterations increases the probability that the largest chunk will be\n   the head (for a list with n items, n iterations guarantees that the\n   largest chunk will be the head).\n\nAllocating\n----------\n\nWhen an allocation request is received, the allocator first attempts to\nsatisfy it with the chunk at the head of the recycler. If that does not\nsucceed, the request is satisfied by the master list instead. Regardless of\nwhich chunk satisfied the request, the recycler is always cycled.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feapache%2Fwof_alloc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feapache%2Fwof_alloc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feapache%2Fwof_alloc/lists"}