Skip to content

Commit

Permalink
Merge pull request #3 from CliMA/dy/docs
Browse files Browse the repository at this point in the history
Add docs page and more tests
  • Loading branch information
dennisYatunin authored Apr 2, 2024
2 parents b48fde8 + 8b0de52 commit 5c18833
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 153 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/DocCleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Doc Preview Cleanup

on:
pull_request:
types: [closed]

jobs:
doc-preview-cleanup:
runs-on: ubuntu-latest
steps:
- name: Checkout gh-pages branch
uses: actions/checkout@v4
with:
ref: gh-pages

- name: Delete preview and history
run: |
git config user.name "Documenter.jl"
git config user.email "[email protected]"
git rm -rf "previews/PR$PRNUM"
git commit -m "delete preview"
git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
env:
PRNUM: ${{ github.event.number }}

- name: Push changes
run: |
git push --force origin gh-pages-new:gh-pages
28 changes: 28 additions & 0 deletions .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Documentation

on:
push:
branches:
- main
tags: '*'
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docbuild:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: '1.10'
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key
run: julia --project=docs/ docs/make.jl
7 changes: 5 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
name = "UnrolledUtilities"
uuid = "0fe1646c-419e-43be-ac14-22321958931b"
authors = ["CliMA Contributors <[email protected]>"]
version = "0.1.0"
version = "0.1.1"

[deps]

[compat]
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "JET", "OrderedCollections", "PrettyTables", "SafeTestsets", "Test"]
test = ["Aqua", "JET", "InteractiveUtils", "OrderedCollections", "PrettyTables", "SafeTestsets", "Test"]
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
# UnrolledUtilities.jl
A Julia package that provides unrolled analogues to functions from `Base` and `Base.Iterators`

|||
|---------------------:|:----------------------------------------------|
| **Documentation** | [![dev][docs-dev-img]][docs-dev-url] |
| **Docs Build** | [![docs build][docs-bld-img]][docs-bld-url] |
| **GHA CI** | [![gha ci][gha-ci-img]][gha-ci-url] |
| **Code Coverage** | [![codecov][codecov-img]][codecov-url] |

[docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg
[docs-dev-url]: https://CliMA.github.io/UnrolledUtilities.jl/dev/

[docs-bld-img]: https://github.com/CliMA/UnrolledUtilities.jl/actions/workflows/Documentation.yml/badge.svg
[docs-bld-url]: https://github.com/CliMA/UnrolledUtilities.jl/actions/workflows/Documentation.yml

[gha-ci-img]: https://github.com/CliMA/UnrolledUtilities.jl/actions/workflows/ci.yml/badge.svg
[gha-ci-url]: https://github.com/CliMA/UnrolledUtilities.jl/actions/workflows/ci.yml

[codecov-img]: https://codecov.io/gh/CliMA/UnrolledUtilities.jl/branch/main/graph/badge.svg
[codecov-url]: https://codecov.io/gh/CliMA/UnrolledUtilities.jl

7 changes: 7 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
34 changes: 34 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Documenter

include(joinpath("..", "test", "test_and_analyze.jl"))

comparison_table_file = joinpath("docs", "src", "comparison_table.md")

open(comparison_table_file, "w") do io
println(io, "# Comparison Table\n```@raw html")
println(io, "<div style=\"width: max(80vw, 100%)\">") # use 80% of viewport
print_comparison_table(io, true)
println(io, "</div>")
println(io, "```")
end

makedocs(;
sitename = "UnrolledUtilities.jl",
modules = [UnrolledUtilities],
pages = ["Home" => "index.md", "Comparison Table" => "comparison_table.md"],
format = Documenter.HTML(
prettyurls = get(ENV, "CI", nothing) == "true",
size_threshold_ignore = ["comparison_table.md"],
),
clean = true,
)

rm(comparison_table_file)

deploydocs(
repo = "github.com/CliMA/UnrolledUtilities.jl.git",
target = "build",
devbranch = "main",
push_preview = true,
forcepush = true,
)
72 changes: 72 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# UnrolledUtilities.jl

A collection of generated functions in which all loops are unrolled and inlined:
- `unrolled_any(f, itr)`: similar to `any`
- `unrolled_all(f, itr)`: similar to `all`
- `unrolled_foreach(f, itrs...)`: similar to `foreach`
- `unrolled_map(f, itrs...)`: similar to `map`
- `unrolled_reduce(op, itr; [init])`: similar to `reduce`
- `unrolled_mapreduce(f, op, itrs...; [init])`: similar to `mapreduce`
- `unrolled_zip(itrs...)`: similar to `zip`
- `unrolled_in(item, itr)`: similar to `in`
- `unrolled_unique(itr)`: similar to `unique`
- `unrolled_filter(f, itr)`: similar to `filter`
- `unrolled_split(f, itr)`: similar to `(filter(f, itr), filter(!f, itr))`, but
without duplicate calls to `f`
- `unrolled_flatten(itr)`: similar to `Iterators.flatten`
- `unrolled_flatmap(f, itrs...)`: similar to `Iterators.flatmap`
- `unrolled_product(itrs...)`: similar to `Iterators.product`
- `unrolled_take(itr, ::Val{N})`: similar to `Iterators.take` or `itr[1:N]`, but
with `N` wrapped in a `Val`
- `unrolled_drop(itr, ::Val{N})`: similar to `Iterators.drop` or
`itr[(N + 1):end]`, but with `N` wrapped in a `Val`

These functions are guaranteed to be type-stable whenever they are given
iterators with inferrable lengths and element types, including when
- the iterators have many elements (e.g., more than 32, which is when `map`,
`reduce`, and `mapreduce` tend to stop getting compiled efficiently)
- the iterators have nonuniform element types (most functions from `Base` and
`Base.Iterators` tend to encounter type-instabilities and allocations when
this is the case, especially when there are more than 32 elements)
- `f` and/or `op` recursively call the function to which they are passed, up to
an arbitrarily large recursion depth (e.g., if `f` calls `map(f, itrs)`, it
will be type-unstable when the recursion depth exceeds 2, but this will not be
the case with `unrolled_map`)

In addition, these functions have been written in a way that makes them very
likely to get fully optimized out through constant propagation when the
iterators have singleton element types (and when the result of calling `f`
and/or `op` on these elements is inferrable). However, they can also be much
more expensive to compile than their counterparts from `Base` and
`Base.Iterators`, in which case they should not be used unless there is a clear
performance benefit. Some notable exceptions to this are `unrolled_zip`,
`unrolled_take`, and `unrolled_drop`, which tend to be easier to compile than
`zip`, `Iterators.take`, `Iterators.drop`, and standard indexing notation.

For a more precise indication of whether you should use `UnrolledUtilities`,
please consult the autogenerated [Comparison Table](@ref). This table contains a
comprehensive set of potential use cases, each with a measurement of performance
optimization, the time required for compilation, and the memory usage during
compilation. Most cases involve simple functions `f` and/or `op`, but the last
few demonstrate the benefits of unrolling with non-trivial recursive functions.

The rows of the table are highlighted as follows:
- green indicates an improvement in performance and either no change in
compilation or easier compilation (i.e., either similar or smaller values of
compilation time and memory usage)
- dark blue indicates an improvement in performance and harder compilation
(i.e., larger values of compilation time and/or memory usage)
- light blue indicates no change in performance and easier compilation
- yellow indicates no change in performance and no change in compilation
- magenta indicates no change in performance, an increase in compilation time,
and a decrease in compilation memory usage
- red indicates no change in performance and harder compilation

Rows highlighted in green and blue present a clear advantage for unrolling,
whereas those highlighted in yellow, magenta, and red either have no clear
advantage, or they have a clear disadvantage. It is recommended that you only
unroll when your use case is similar to a row in the first category.

The table is also printed out by this package's unit tests, so these
measurements can be compared across different operating systems by checking the
[CI pipeline](https://github.com/CliMA/UnrolledUtilities.jl/actions/workflows/ci.yml).
48 changes: 3 additions & 45 deletions src/UnrolledUtilities.jl
Original file line number Diff line number Diff line change
@@ -1,45 +1,3 @@
"""
UnrolledUtilities
A collection of generated functions in which all loops are unrolled and inlined.
The functions exported by this module are
- `unrolled_any(f, itr)`: similar to `any`
- `unrolled_all(f, itr)`: similar to `all`
- `unrolled_foreach(f, itrs...)`: similar to `foreach`
- `unrolled_map(f, itrs...)`: similar to `map`
- `unrolled_reduce(op, itr; [init])`: similar to `reduce`
- `unrolled_mapreduce(f, op, itrs...; [init])`: similar to `mapreduce`
- `unrolled_zip(itrs...)`: similar to `zip`
- `unrolled_in(item, itr)`: similar to `in`
- `unrolled_unique(itr)`: similar to `unique`
- `unrolled_filter(f, itr)`: similar to `filter`
- `unrolled_split(f, itr)`: similar to `(filter(f, itr), filter(!f, itr))`, but
without duplicate calls to `f`
- `unrolled_flatten(itr)`: similar to `Iterators.flatten`
- `unrolled_flatmap(f, itrs...)`: similar to `Iterators.flatmap`
- `unrolled_product(itrs...)`: similar to `Iterators.product`
- `unrolled_take(itr, ::Val{N})`: similar to `Iterators.take`, but with the
second argument wrapped in a `Val`
- `unrolled_drop(itr, ::Val{N})`: similar to `Iterators.drop`, but with the
second argument wrapped in a `Val`
These functions are guaranteed to be type-stable whenever they are given
iterators with inferrable lengths and element types, including when
- the iterators have nonuniform element types (with the exception of `map`, all
of the corresponding functions from `Base` encounter type-instabilities and
allocations when this is the case)
- the iterators have many elements (e.g., more than 32, which is the threshold
at which `map` becomes type-unstable for `Tuple`s)
- `f` and/or `op` recursively call the function to which they is passed, with an
arbitrarily large recursion depth (e.g., if `f` calls `map(f, itrs)`, it will
be type-unstable when the recursion depth exceeds 3, but this will not be the
case with `unrolled_map`)
Moreover, these functions are very likely to be optimized out through constant
propagation when the iterators have singleton element types (and when the result
of calling `f` and/or `op` on these elements is inferrable).
"""
module UnrolledUtilities

export unrolled_any,
Expand Down Expand Up @@ -73,7 +31,7 @@ function zipped_f_exprs(itr_types)
return (:(f($((:(itrs[$l][$n]) for l in 1:L)...))) for n in 1:N)
end
@inline @generated unrolled_foreach(f, itrs...) =
Expr(:block, zipped_f_exprs(itrs)...)
Expr(:block, zipped_f_exprs(itrs)..., nothing)
@inline @generated unrolled_map(f, itrs...) =
Expr(:tuple, zipped_f_exprs(itrs)...)

Expand All @@ -89,8 +47,8 @@ struct NoInit end
@inline unrolled_reduce(op, itr; init = NoInit()) =
unrolled_reduce_without_init(op, init isa NoInit ? itr : (init, itr...))

@inline unrolled_mapreduce(f, op, itrs...; init_kwarg...) =
unrolled_reduce(op, unrolled_map(f, itrs...); init_kwarg...)
@inline unrolled_mapreduce(f, op, itrs...; init = NoInit()) =
unrolled_reduce(op, unrolled_map(f, itrs...); init)

@inline unrolled_zip(itrs...) = unrolled_map(tuple, itrs...)

Expand Down
11 changes: 7 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using SafeTestsets

#! format: off
@safetestset "Test and Analyze" begin @time include("test_and_analyze.jl") end
@safetestset "Aqua" begin @time include("aqua.jl") end
#! format: on
@safetestset "Test and Analyze" begin
@time include("test_and_analyze.jl")
print_comparison_table()
end
@safetestset "Aqua" begin
@time include("aqua.jl")
end
Loading

2 comments on commit 5c18833

@dennisYatunin
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/104049

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.1.1 -m "<description of version>" 5c18833fe3ced748f47a9720864b6888f53ff2b7
git push origin v0.1.1

Please sign in to comment.