{"id":15713558,"url":"https://github.com/homonoidian/permafrost","last_synced_at":"2026-06-17T12:31:45.263Z","repository":{"id":205861260,"uuid":"715269369","full_name":"homonoidian/permafrost","owner":"homonoidian","description":"Permafrost is a collection of thread-safe, persistent, immutable data structures for Crystal.","archived":false,"fork":false,"pushed_at":"2026-05-30T17:19:14.000Z","size":1957,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"trunk","last_synced_at":"2026-05-30T19:09:59.790Z","etag":null,"topics":["bitset","crystal","hamt","hashmap","immutable","persistent-data-structure"],"latest_commit_sha":null,"homepage":"https://homonoidian.github.io/permafrost/","language":"Crystal","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/homonoidian.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-11-06T20:00:02.000Z","updated_at":"2026-05-30T17:18:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"5ea7b519-03a8-4395-b920-156e8112063b","html_url":"https://github.com/homonoidian/permafrost","commit_stats":null,"previous_names":["homonoidian/permafrost"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/homonoidian/permafrost","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homonoidian%2Fpermafrost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homonoidian%2Fpermafrost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homonoidian%2Fpermafrost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homonoidian%2Fpermafrost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/homonoidian","download_url":"https://codeload.github.com/homonoidian/permafrost/tar.gz/refs/heads/trunk","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homonoidian%2Fpermafrost/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34449277,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["bitset","crystal","hamt","hashmap","immutable","persistent-data-structure"],"created_at":"2024-10-03T21:32:05.709Z","updated_at":"2026-06-17T12:31:45.258Z","avatar_url":"https://github.com/homonoidian.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"# permafrost\n\nPermafrost is a collection of thread-safe, persistent, immutable data structures for Crystal:\n\n- an unordered map `Pf::Map`,\n- an unordered set `Pf::Set`,\n- an unordered bidirectional map (bimap) `Pf::BidiMap`,\n- an unsigned 32-bit integer set `Pf::USet32`,\n- a grapheme string selection type `Pf::GraphemeSeln`,\n- and others...\n\nMost of the data structures in Permafrost come from my main project, [Wirewright](https://github.com/wirewright/wirewright).\nWirewright is also the main user of Permafrost.\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n   ```yaml\n   dependencies:\n     permafrost:\n       github: homonoidian/permafrost\n   ```\n\n2. Run `shards install`\n\n## Usage\n\nSee [the docs](https://homonoidian.github.io/permafrost/). \n\n\u003e [!NOTE]\n\u003e Some safety checks are disabled in release mode. You can compile with `-Dsafe`\n\u003e to enable them in release mode.\n\n## Performance\n\nBenchmarks in `benchmark/` require Immutable for comparison. Run `shards install` while\nin `benchmarks/` to install it.\n\nMy CPU: Ryzen 3 2200G.\n\n\u003e [!NOTE]\n\u003e You are recommended to compile the benchmarks with `--mcpu=native` to make sure the Crystal\n\u003e compiler and LLVM consider vectorization if your CPU supports it (this also enables optimizations\n\u003e specific to your CPU). I'm saying this because the benchmarks perform slightly worse without this\n\u003e flag on my machine.\n\u003e\n\u003e Since Permafrost is GC-heavy you are recommended to use `-Dpreview_mt`. An extremely efficient\n\u003e immutable map, the best one you can imagine, is basically a hair-thin wrapper around malloc.\n\u003e The GC/allocator you use plays a very important role here, and whether it runs on multiple threads etc.\n\n### Map\n\nSee `benchmark/map.cr` for `Pf::Map` benchmark source code.\n\n```text\n$ crystal build map.cr -Dpreview_mt --release --mcpu native\n```\n\n```text\nHash (speed of light): add 275.57  (  3.63ms) (±16.29%)  4.75MB/op        fastest\n          Pf::Map: add txn  49.50  ( 20.20ms) (±14.59%)  7.79MB/op   5.57× slower\n              Pf::Map: add   8.38  (119.32ms) (± 9.15%)    98MB/op  32.88× slower\n   Immutable::Map: add txn   5.82  (171.76ms) (±10.19%)   101MB/op  47.33× slower\n       Immutable::Map: add   3.39  (295.09ms) (±11.34%)   219MB/op  81.32× slower\n\nHash (speed of light): add + delete 156.36  (  6.40ms) (±13.24%)  4.75MB/op        fastest\n          Pf::Map: add + delete txn  32.74  ( 30.54ms) (±15.19%)  7.79MB/op   4.78× slower\n              Pf::Map: add + delete   4.20  (238.05ms) (± 5.24%)   189MB/op  37.22× slower\n   Immutable::Map: add + delete txn   3.07  (325.23ms) (± 1.59%)   133MB/op  50.85× slower\n       Immutable::Map: add + delete   1.64  (608.61ms) (± 0.84%)   373MB/op  95.16× slower\n\nHash (speed of light): each   9.38k (106.61µs) (± 0.93%)    0.0B/op          fastest\n              Pf::Map: each 271.59  (  3.68ms) (± 1.33%)    0.0B/op    34.54× slower\n       Immutable::Map: each   6.16  (162.22ms) (± 4.29%)  87.4MB/op  1521.65× slower\n\nHash (speed of light): word max  28.73  ( 34.80ms) (± 8.71%)  3.75MB/op        fastest\n          Pf::Map: word max txn   9.87  (101.29ms) (±16.64%)  25.7MB/op   2.91× slower\n          Pf::Map: word max imm   1.94  (515.01ms) (± 2.38%)   395MB/op  14.80× slower\n   Immutable::Map: word max imm 713.24m (  1.40s ) (± 1.57%)   870MB/op  40.28× slower\n\nHash (speed of light): bigram bag jaccard  15.11  ( 66.18ms) (± 1.97%)   522kB/op        fastest\n          Pf::Map: bigram bag jaccard txn   4.68  (213.87ms) (± 6.50%)  87.5MB/op   3.23× slower\n          Pf::Map: bigram bag jaccard imm 529.40m (  1.89s ) (± 0.72%)   1.7GB/op  28.54× slower\n   Immutable::Map: bigram bag jaccard imm 188.77m (  5.30s ) (± 0.00%)  3.83GB/op  80.04× slower\n```\n\n### USet32\n\nSee `benchmark/uset32.cr` for `Pf::USet32` benchmark source code.\n\n```text\n$ crystal build uset32.cr -Dpreview_mt --release --mcpu native\n```\n\n```text\nxs.size = 100k\nys.size = 20.0k\nzs.size = 70.0k\nps.size = 60.0k\n\n   Set: create 100k 498.90  (  2.00ms) (± 1.24%)  2.0MB/op        fastest\nUSet32: create 100k 366.27  (  2.73ms) (± 0.44%)  231kB/op   1.36× slower\n\nUSet32: subset ys xs   1.98M (504.31ns) (± 2.09%)  32.0B/op         fastest\n   Set: subset ys xs   2.71k (369.01µs) (± 0.39%)   0.0B/op  731.72× slower\n\nUSet32: xs union zs   1.22M (818.00ns) (± 3.89%)  0.99kB/op          fastest\n   Set: xs union zs 188.43  (  5.31ms) (± 1.55%)   6.0MB/op  6487.92× slower\n\nUSet32: union subset xs ys   1.97M (506.75ns) (± 1.87%)  32.0B/op          fastest\n   Set: union subset xs ys 413.52  (  2.42ms) (± 1.84%)  2.0MB/op  4772.15× slower\n\n   Set: xs intersects? zs (true)  89.44M ( 11.18ns) (± 1.49%)  0.0B/op        fastest\nUSet32: xs intersects? zs (true)  85.52M ( 11.69ns) (± 1.77%)  0.0B/op   1.05× slower\n\nUSet32: xs intersects? ps (false) 103.59M (  9.65ns) (± 1.40%)  0.0B/op            fastest\n   Set: xs intersects? ps (false) 886.85  (  1.13ms) (± 0.62%)  0.0B/op  116802.34× slower\n\nUSet32: xs intersect zs 689.10k (  1.45µs) (± 2.87%)  2.79kB/op          fastest\n   Set: xs intersect zs 360.09  (  2.78ms) (± 0.85%)   769kB/op  1913.68× slower\n\nUSet32: xs difference zs   1.27M (785.97ns) (± 3.57%)    608B/op          fastest\n   Set: xs difference zs 156.01  (  6.41ms) (± 2.77%)  3.75MB/op  8155.08× slower\n\nUSet32: Jaccard xs zs 421.19k (  2.37µs) (± 2.16%)  3.78kB/op          fastest\n   Set: Jaccard xs zs 106.82  (  9.36ms) (± 1.87%)  6.75MB/op  3943.10× slower\n```\n\nSee also the docs for `Pf::USet32` for related prose.\n\n\u003e [!TODO]\n\u003e Compare with BitArray and CRoaring. We will likely win over BitArray due to copying\n\u003e overhead but lose to CRoaring due to indirection, although probably only on large\n\u003e bitmaps (whatever that means).\n\n## Development\n\n- If you find any errors please let me know or (even better!) fix them yourself and\n  submit a PR.\n- New methods or quality improvements are welcome, especially if they already exist in\n  Crystal's stdlib.\n- Optimizations are *especially* welcome. E.g. Wirewright currently uses Permafrost in\n  extremely hot places. A dozen nanoseconds shaved off of something hot in Permafrost itself\n  would be very nice.\n\n## References\n\n- Michael J. Steindorfer, Jurgen J. Vinju, [Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections](https://michael.steindorfer.name/publications/oopsla15.pdf)\n- Phil Bagwell, [Ideal Hash Trees](https://lampwww.epfl.ch/papers/idealhashtrees.pdf)\n- [Immutable](https://github.com/lucaong/immutable)\n- [HAMT for C with good internals explanation](https://github.com/mkirchner/hamt)\n- [Clojure's PersistentHashMap](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentHashMap.java)\n- [Roaring bitmaps](https://roaringbitmap.org/)\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/homonoidian/permafrost/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Contributors\n\n- [Alexey Yurchenko](https://github.com/homonoidian) - creator and maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomonoidian%2Fpermafrost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhomonoidian%2Fpermafrost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomonoidian%2Fpermafrost/lists"}