{"id":17113017,"url":"https://github.com/emmt/quickheaps.jl","last_synced_at":"2025-04-09T20:33:47.334Z","repository":{"id":39412141,"uuid":"417173667","full_name":"emmt/QuickHeaps.jl","owner":"emmt","description":"Faster binary heaps for Julia","archived":false,"fork":false,"pushed_at":"2024-05-15T12:04:40.000Z","size":379,"stargazers_count":10,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-23T22:34:40.500Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emmt.png","metadata":{"files":{"readme":"README.md","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2021-10-14T14:58:58.000Z","updated_at":"2024-09-04T15:35:51.000Z","dependencies_parsed_at":"2024-05-16T00:51:13.111Z","dependency_job_id":null,"html_url":"https://github.com/emmt/QuickHeaps.jl","commit_stats":{"total_commits":84,"total_committers":1,"mean_commits":84.0,"dds":0.0,"last_synced_commit":"c3752c809ed9c2d94d819ae1c75cc84c05db2113"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FQuickHeaps.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FQuickHeaps.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FQuickHeaps.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FQuickHeaps.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emmt","download_url":"https://codeload.github.com/emmt/QuickHeaps.jl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248107873,"owners_count":21049025,"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-10-14T17:02:19.097Z","updated_at":"2025-04-09T20:33:47.289Z","avatar_url":"https://github.com/emmt.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Versatile binary heaps and priority queues for Julia\n\n[![Doc. Stable][doc-stable-img]][doc-stable]\n[![Doc. Devel][doc-dev-img]][doc-dev]\n[![License][license-img]][license-url]\n[![Build Status][github-ci-img]][github-ci-url]\n[![Build Status][appveyor-img]][appveyor-url]\n[![Coverage][codecov-img]][codecov-url]\n\n`QuickHeaps` is a small [Julia][julia-url] package providing versatile [binary\nheaps](https://en.wikipedia.org/wiki/Binary_heap) and [priority\nqueues](https://en.wikipedia.org/wiki/Priority_queue). These data structures\nare more flexible and may be quite significantly faster than those provided by\n[`DataStructures`][datastructures-url].\n\n## Installation\n\nThe easiest way to install `QuickHeaps` is to use [Julia's package\nmanager](https://pkgdocs.julialang.org/):\n\n```julia\nusing Pkg\npkg\"add QuickHeaps\"\n```\n\n## Documentation\n\nDocumentation is available for different versions of `QuickHeaps`:\n\n- [last release][doc-stable];\n\n- [development version][doc-stable].\n\n\n## Speed up and strengthen sorting algorithms\n\nThe sorting algorithms in Julia are very powerful but have some issues (for\nme):\n\n1. Sorting algorithms involve lots of comparisons and could be much faster if\n   we know that there are no NaN's in the arrays to sort or if we assume (at\n   our own risk) that they can be ignored.\n\n2. Some non-exported methods may be very useful if they can be safely\n   called.\n\nThis short note is bout dealing with these two points.\n\n\n### Speed up sorting\n\nSorting algorithms in Julia rely on `Base.Order.lt(o,x,y)` to check whether `x`\nis *before* `y` according to the ordering specified in `o` (the letters `lt`\nstands for *less than*).  Most (all?) Julia sorting methods have an `order`\nkeyword to specify the ordering.  By default, ordering is `Base.Order.Forward`\nwhich is the singleton of type `Base.Order.ForwardOrdering`.  Another usual\nchoice is to take `Base.Order.Reverse`.  In short, when sorting, you are using\nthe following definitions:\n\n```julia\nconst Forward = ForwardOrdering()\nconst Reverse = ReverseOrdering()\nlt(o::ForwardOrdering, a, b) = isless(a,b)\nlt(o::ReverseOrdering, a, b) = lt(o.fwd,b,a)\n```\n\nSo it turns out that, `isless` is eventually called, not the operator `\u003c`.  For\nintegers `isless(x,y)` and `x \u003c y` are the same (at least as far as execution\ntime is concerned) but for floating-point values, `isless` takes care of NaN's\nwhich involves overheads and this may strongly impact the execution time of\nsorting algorithms.\n\nSimple means to ignore NaN's in sorting consists in defining your own ordering\ntypes and extend the `Base.Order.lt` method, this is pretty simple:\n\n```julia\nusing Base: Ordering, ReverseOrdering\nstruct FastForwardOrdering \u003c: Ordering end\nconst FastForward = FastForwardOrdering()\nconst FastReverse = ReverseOrdering(FastForward)\n\nimport Base: lt\nlt(::FastForwardOrdering, a, b) = a \u003c b\n```\n\nThen just use keyword `order=FastForward` or `order=FastReverse` in calls to\nsort methods to benefit from a speed-up factor between 2 or 3.  Almost for free!\nThe very same trick has been implemented in the\n[`DataStructures`](https://github.com/JuliaCollections/DataStructures.jl)\npackage with `DataStructures.FasterForward()` and\n`DataStructures.FasterReverse()`.\n\nChecking that an array has NaN's can be checked in linear time, that is `O(n)`,\nfor arrays of `n` floating-point values by the following method:\n\n```julia\nfunction has_nans(A::AbstractArray{\u003c:AbstractFloat})\n    flag = false\n    @inbounds @simd for i in eachindex(A)\n        flag |= isnan(A[i])\n    end\n    return flag\nend\n```\n\nwhere short-circuit has been purposely avoided to exploit SIMD optimization.\nThe rationale is that if you are mostly interested in arrays with no NaN's, you\nexpect that the entire array be checked.  A simple benchmark follows:\n\n```julia\nusing BenchmarkTools\nx = rand(1000);\n@btime has_nans($x) # ----\u003e 119.054 ns (0 allocations: 0 bytes)\n```\n\nwhich is much shorter than the time it takes to heapify the array.  This test\ncould be applied to arrays of floating-point values to choose between fast/slow\nordering.  This would not change the behavior of the sorting methods but would\nsignificantly reduce the execution time most of the time.\n\nFor integer-valued arrays, it takes `O(1)` time to check for NaN's:\n\n```julia\nhas_nans(A::AbstractArray{\u003c:Integer}) = false\n```\n\nAn additional speed-up by a factor between 1.5 and 2 is achievable by proper use\nof `@inline`, `@inbounds` and `@propagate_inbounds` macros in the code\nimplementing the sorting algorithms.  This however requires to modify existing\ncode.  This is what is done in\n[`QuickHeaps`](https://github.com/emmt/QuickHeaps.jl).\n\n\n### Application to binary heaps\n\nAs an illustration of the above discussion, below is the output of a small\nbenchmark ran by:\n\n```julia\njulia --project test/benchmarks.jl\n```\n\nwith Julia 1.6.3 on an AMD Ryzen Threadripper 2950X 16-Core Processor:\n\n```\nTimings for \"DataStructures\" methods (T=Float64, n=1000):\n - DataStructures.heapify!(..., Base.Forward) ---------------------\u003e 7.478 μs (0 allocations: 0 bytes)\n - DataStructures.heapify!(..., Base.Reverse) ---------------------\u003e 7.268 μs (0 allocations: 0 bytes)\n - DataStructures.heapify!(..., DataStructures.FasterForward()) ---\u003e 3.444 μs (0 allocations: 0 bytes)\n - DataStructures.heapify!(..., DataStructures.FasterReverse()) ---\u003e 3.428 μs (0 allocations: 0 bytes)\n - DataStructures.heapify!(..., QuickHeaps.FastMin) ---------------\u003e 3.413 μs (0 allocations: 0 bytes)\n - DataStructures.heapify!(..., QuickHeaps.FastMax) ---------------\u003e 3.428 μs (0 allocations: 0 bytes)\n\nTimings for \"QuickHeaps\" methods (T=Float64, n=1000):\n - QuickHeaps.heapify!(..., Base.Forward) -------------------------\u003e 4.852 μs (0 allocations: 0 bytes)\n - QuickHeaps.heapify!(..., Base.Reverse) -------------------------\u003e 4.506 μs (0 allocations: 0 bytes)\n - QuickHeaps.heapify!(..., DataStructures.FasterForward()) -------\u003e 1.655 μs (0 allocations: 0 bytes)\n - QuickHeaps.heapify!(..., DataStructures.FasterReverse()) -------\u003e 1.658 μs (0 allocations: 0 bytes)\n - QuickHeaps.heapify!(..., QuickHeaps.FastMin) -------------------\u003e 1.637 μs (0 allocations: 0 bytes)\n - QuickHeaps.heapify!(..., QuickHeaps.FastMax) -------------------\u003e 1.658 μs (0 allocations: 0 bytes)\n\nTimings for \"DataStructures\" methods (T=Float64, n=1000):\n - DataStructures.isheap(..., Base.Forward) -----------------------\u003e 1.910 μs (0 allocations: 0 bytes)\n - DataStructures.isheap(..., Base.Reverse) -----------------------\u003e 1.932 μs (0 allocations: 0 bytes)\n - DataStructures.isheap(..., DataStructures.FasterForward()) -----\u003e 563.027 ns (0 allocations: 0 bytes)\n - DataStructures.isheap(..., DataStructures.FasterReverse()) -----\u003e 575.110 ns (0 allocations: 0 bytes)\n - DataStructures.isheap(..., QuickHeaps.FastMin) -----------------\u003e 575.087 ns (0 allocations: 0 bytes)\n - DataStructures.isheap(..., QuickHeaps.FastMax) -----------------\u003e 573.750 ns (0 allocations: 0 bytes)\n\nTimings for \"QuickHeaps\" methods (T=Float64, n=1000):\n - QuickHeaps.isheap(..., Base.Forward) ---------------------------\u003e 1.820 μs (0 allocations: 0 bytes)\n - QuickHeaps.isheap(..., Base.Reverse) ---------------------------\u003e 1.821 μs (0 allocations: 0 bytes)\n - QuickHeaps.isheap(..., DataStructures.FasterForward()) ---------\u003e 381.527 ns (0 allocations: 0 bytes)\n - QuickHeaps.isheap(..., DataStructures.FasterReverse()) ---------\u003e 383.847 ns (0 allocations: 0 bytes)\n - QuickHeaps.isheap(..., QuickHeaps.FastMin) ---------------------\u003e 378.627 ns (0 allocations: 0 bytes)\n - QuickHeaps.isheap(..., QuickHeaps.FastMax) ---------------------\u003e 384.631 ns (0 allocations: 0 bytes)\n```\n\nThese timings show the gain in speed for `heapify!` by using `\u003c` instead of\n`isless` by a factor of 2.3 for the binary heap implemented by `DataStructures`\nand by a factor of 3.2 for the binary heap implemented by `QuickHeaps`.\n\nThese timings also show that `heapify!` in `QuickHeaps` is faster than in\n`DataStructures` by a factor greater than 1.5 with standard orderings and by a\nfactor better than 2 with faster orderings.\n\n\n[julia-url]: https://julialang.org/\n[datastructures-url]: https://github.com/JuliaCollections/DataStructures.jl\n\n[license-url]: ./LICENSE.md\n[license-img]: http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat\n\n[doc-stable]: https://emmt.github.io/QuickHeaps.jl/stable\n[doc-dev]: https://emmt.github.io/QuickHeaps.jl/dev\n\n[doc-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg\n[doc-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg\n\n[github-ci-img]: https://github.com/emmt/QuickHeaps.jl/actions/workflows/CI.yml/badge.svg?branch=master\n[github-ci-url]: https://github.com/emmt/QuickHeaps.jl/actions/workflows/CI.yml?query=branch%3Amaster\n\n[appveyor-img]: https://ci.appveyor.com/api/projects/status/github/emmt/QuickHeaps.jl?branch=master\n[appveyor-url]: https://ci.appveyor.com/project/emmt/QuickHeaps-jl/branch/master\n\n[codecov-img]: http://codecov.io/github/emmt/QuickHeaps.jl/coverage.svg?branch=master\n[codecov-url]: http://codecov.io/github/emmt/QuickHeaps.jl?branch=master\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Fquickheaps.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femmt%2Fquickheaps.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Fquickheaps.jl/lists"}