├── .github └── workflows │ ├── deploy.yml │ └── test.yml ├── .mergify.yml ├── Makefile ├── Manifest.toml ├── Project.toml ├── README.md ├── bin ├── runtests.jl └── serve ├── scripts ├── collatz.jl ├── false_sharing_demo.jl └── perf_c2c_demo.jl └── src ├── .gitignore ├── 404.md ├── _assets ├── hamburger.svg ├── juliafolds-logo.ico ├── rndimg.jpg ├── scripts │ ├── generate_results.jl │ ├── output │ │ ├── script1.out │ │ └── script2.svg │ ├── script1.jl │ └── script2.jl └── tutorials │ └── mutations │ ├── accidental_mutations_1_threads.jl │ ├── accidental_mutations_2_seq.jl │ ├── accidental_mutations_3_threads_fixed.jl │ ├── accidental_mutations_4_floop.jl │ ├── accidental_mutations_5_floop_fixed.jl │ ├── adjoining_trick_1.jl │ ├── adjoining_trick_2.jl │ ├── combining_containers_1.jl │ ├── combining_containers_2.jl │ ├── combining_containers_3.jl │ ├── example_1.jl │ ├── example_2.jl │ ├── example_3.jl │ ├── example_4.jl │ ├── example_5.jl │ ├── filling_1.jl │ ├── filling_2.jl │ ├── filling_3.jl │ ├── floops_init_1.jl │ ├── floops_reduce_1.jl │ ├── floops_reduce_2.jl │ ├── floops_reduce_3.jl │ ├── floops_reduce_4.jl │ ├── floops_reduce_5.jl │ ├── floops_reduce_6_vcat.jl │ ├── floops_reduce_do_1.jl │ ├── floops_reduce_do_2.jl │ ├── floops_reduce_do_3.jl │ ├── floops_reduce_do_4.jl │ ├── floops_reduce_do_correct.jl │ ├── floops_reduce_do_incorrect.jl │ ├── floops_reduce_do_os_1.jl │ ├── floops_reduce_do_os_2.jl │ ├── ownership_passing_style_second_1_intro.jl │ ├── ownership_passing_style_second_2_wrong.jl │ ├── ownership_passing_style_second_3_correct.jl │ ├── test_mutations.jl │ └── transducers_oninit.jl ├── _css ├── basic.css └── franklin.css ├── _layout ├── foot.html ├── foot_highlight.html ├── foot_katex.html ├── head.html ├── head_highlight.html ├── head_katex.html ├── header.html ├── page_foot.html └── tag.html ├── _libs ├── highlight │ ├── github.min.css │ └── highlight.pack.js └── katex │ ├── auto-render.min.js │ ├── fonts │ ├── KaTeX_AMS-Regular.ttf │ ├── KaTeX_AMS-Regular.woff │ ├── KaTeX_AMS-Regular.woff2 │ ├── KaTeX_Caligraphic-Bold.ttf │ ├── KaTeX_Caligraphic-Bold.woff │ ├── KaTeX_Caligraphic-Bold.woff2 │ ├── KaTeX_Caligraphic-Regular.ttf │ ├── KaTeX_Caligraphic-Regular.woff │ ├── KaTeX_Caligraphic-Regular.woff2 │ ├── KaTeX_Fraktur-Bold.ttf │ ├── KaTeX_Fraktur-Bold.woff │ ├── KaTeX_Fraktur-Bold.woff2 │ ├── KaTeX_Fraktur-Regular.ttf │ ├── KaTeX_Fraktur-Regular.woff │ ├── KaTeX_Fraktur-Regular.woff2 │ ├── KaTeX_Main-Bold.ttf │ ├── KaTeX_Main-Bold.woff │ ├── KaTeX_Main-Bold.woff2 │ ├── KaTeX_Main-BoldItalic.ttf │ ├── KaTeX_Main-BoldItalic.woff │ ├── KaTeX_Main-BoldItalic.woff2 │ ├── KaTeX_Main-Italic.ttf │ ├── KaTeX_Main-Italic.woff │ ├── KaTeX_Main-Italic.woff2 │ ├── KaTeX_Main-Regular.ttf │ ├── KaTeX_Main-Regular.woff │ ├── KaTeX_Main-Regular.woff2 │ ├── KaTeX_Math-BoldItalic.ttf │ ├── KaTeX_Math-BoldItalic.woff │ ├── KaTeX_Math-BoldItalic.woff2 │ ├── KaTeX_Math-Italic.ttf │ ├── KaTeX_Math-Italic.woff │ ├── KaTeX_Math-Italic.woff2 │ ├── KaTeX_SansSerif-Bold.ttf │ ├── KaTeX_SansSerif-Bold.woff │ ├── KaTeX_SansSerif-Bold.woff2 │ ├── KaTeX_SansSerif-Italic.ttf │ ├── KaTeX_SansSerif-Italic.woff │ ├── KaTeX_SansSerif-Italic.woff2 │ ├── KaTeX_SansSerif-Regular.ttf │ ├── KaTeX_SansSerif-Regular.woff │ ├── KaTeX_SansSerif-Regular.woff2 │ ├── KaTeX_Script-Regular.ttf │ ├── KaTeX_Script-Regular.woff │ ├── KaTeX_Script-Regular.woff2 │ ├── KaTeX_Size1-Regular.ttf │ ├── KaTeX_Size1-Regular.woff │ ├── KaTeX_Size1-Regular.woff2 │ ├── KaTeX_Size2-Regular.ttf │ ├── KaTeX_Size2-Regular.woff │ ├── KaTeX_Size2-Regular.woff2 │ ├── KaTeX_Size3-Regular.ttf │ ├── KaTeX_Size3-Regular.woff │ ├── KaTeX_Size3-Regular.woff2 │ ├── KaTeX_Size4-Regular.ttf │ ├── KaTeX_Size4-Regular.woff │ ├── KaTeX_Size4-Regular.woff2 │ ├── KaTeX_Typewriter-Regular.ttf │ ├── KaTeX_Typewriter-Regular.woff │ └── KaTeX_Typewriter-Regular.woff2 │ ├── katex.min.css │ └── katex.min.js ├── config.md ├── explanation ├── index.html ├── libraries.md └── semidirect-products.md ├── howto ├── faq.md └── index.html ├── index.md ├── reference └── index.html ├── tutorials ├── concurrency-patterns.md ├── index.html ├── mutations.md └── quick-introduction.md └── utils.jl /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | # NOTE: 5 | # For a **project** site (username.github.io/project/), push things 6 | # to the **master** branch and make sure to set the line below to 7 | # ` - master`; also, at the end of the file, change to `BRANCH: gh-pages` 8 | # 9 | # For a **personal** site (username.github.io/), push things to a **dev** 10 | # branch and make sure to set the line below to `- dev` this is 11 | # because for user pages GitHub pages **requires** the deployment to be 12 | # on the master branch; also, at the end of the file, change to 13 | # `BRANCH: master` 14 | branches: 15 | - master 16 | - dev 17 | pull_request: 18 | jobs: 19 | build-and-deploy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | with: 25 | persist-credentials: false 26 | - name: Install SSH Client 27 | continue-on-error: true 28 | if: >- 29 | github.event_name != 'pull_request' || 30 | github.repository == github.event.pull_request.head.repo.full_name 31 | # Checking against github.repository to avoid failing in PRs 32 | # from forks. 33 | uses: webfactory/ssh-agent@v0.4.1 34 | with: 35 | ssh-private-key: ${{ secrets.FRANKLIN_PRIV }} 36 | 37 | - name: Fix URLs for PR preview deployment (pull request previews) 38 | if: github.event_name == 'pull_request' 39 | run: | 40 | echo "JULIA_FRANKLIN_WEBSITE_URL=https://juliafolds.github.io/data-parallelism/previews/PR${{ github.event.number }}/" >> $GITHUB_ENV 41 | echo "JULIA_FRANKLIN_PREPATH=data-parallelism/previews/PR${{ github.event.number }}" >> $GITHUB_ENV 42 | - run: echo $JULIA_FRANKLIN_WEBSITE_URL 43 | - run: echo $JULIA_FRANKLIN_PREPATH 44 | 45 | # NOTE 46 | # Python is necessary for pre-rendering steps as well as to install 47 | # matplotlib which is necessary if you intend to use PyPlot. If you do 48 | # not, then you can remove the `run: pip install matplotlib` line. 49 | - name: Install python 50 | uses: actions/setup-python@v1 51 | with: 52 | python-version: '3.x' 53 | - name: Install Julia 54 | uses: julia-actions/setup-julia@v1 55 | with: 56 | version: 1.6 57 | # NOTE 58 | # The steps below ensure that NodeJS and Franklin are loaded then it 59 | # installs highlight.js which is needed for the prerendering step. 60 | # Then the environment is activated and instantiated to install all 61 | # Julia packages which may be required to successfully build your site. 62 | # 63 | # The last line should be `optimize()` though you may want to give it 64 | # specific arguments, see the documentation or ?optimize in the REPL. 65 | - run: julia --project=. -e 'using Pkg; Pkg.instantiate();' 66 | - run: julia --project=. -e ' 67 | cd("src"); 68 | using NodeJS; 69 | run(`$(npm_cmd()) install highlight.js`);' 70 | - run: julia --project=. -e ' 71 | cd("src"); 72 | using Franklin; 73 | optimize()' 74 | env: 75 | GKSwstype: "nul" 76 | 77 | - name: Deploy (preview) 78 | if: >- 79 | github.event_name == 'pull_request' && 80 | github.repository == github.event.pull_request.head.repo.full_name 81 | # Checking against github.repository to avoid failing in PRs 82 | # from forks. 83 | uses: JamesIves/github-pages-deploy-action@releases/v3 84 | with: 85 | SSH: true 86 | BRANCH: gh-pages 87 | FOLDER: src/__site 88 | TARGET_FOLDER: previews/PR${{ github.event.number }} 89 | 90 | # https://github.com/niteoweb/pull_request_status_action 91 | - name: Set PR status 92 | if: >- 93 | github.event_name == 'pull_request' && 94 | github.repository == github.event.pull_request.head.repo.full_name 95 | uses: niteoweb/pull_request_status_action@v1.0.0 96 | with: 97 | pr_number: ${{ github.event.number }} 98 | state: success 99 | repository: ${{ github.repository }} 100 | target_url: "https://juliafolds.github.io/data-parallelism/previews/PR${{ github.event.number }}/" 101 | description: "preview is successfully deployed" 102 | context: Franklin/deploy 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 105 | 106 | - name: Check build status 107 | run: >- 108 | julia -e 'read("src/.test-result", String) == "" ? nothing : exit(99)' 109 | 110 | - name: Deploy (main) 111 | if: github.event_name == 'push' 112 | uses: JamesIves/github-pages-deploy-action@releases/v3 113 | with: 114 | SSH: true 115 | BRANCH: gh-pages 116 | FOLDER: src/__site 117 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: '*' 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | name: Test 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup julia 17 | uses: julia-actions/setup-julia@v1 18 | with: 19 | version: '1.6' 20 | - run: make test 21 | env: 22 | JULIA_NUM_THREADS: '2' 23 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # [NOTE] This setting relies on "required status check": 2 | # https://docs.github.com/en/github/administering-a-repository/about-required-status-checks 3 | 4 | pull_request_rules: 5 | - name: remove outdated reviews 6 | conditions: 7 | - base=master 8 | actions: 9 | dismiss_reviews: {} 10 | - name: automatic squash-merge when CI passes 11 | conditions: 12 | - base=master 13 | - "#approved-reviews-by>=1" 14 | - label=ready-to-merge:squash 15 | - label!=work-in-progress 16 | actions: 17 | merge: 18 | method: squash 19 | - name: automatic squash-merge when CI passes (@tkf) 20 | conditions: 21 | - base=master 22 | - author=tkf 23 | - label=ready-to-merge:squash 24 | - label!=work-in-progress 25 | actions: 26 | merge: 27 | method: squash 28 | - name: automatic rebase-merge when CI passes 29 | conditions: 30 | - base=master 31 | - "#approved-reviews-by>=1" 32 | - label=ready-to-merge:rebase 33 | - label!=work-in-progress 34 | actions: 35 | merge: 36 | method: rebase 37 | - name: automatic rebase-merge when CI passes (@tkf) 38 | conditions: 39 | - base=master 40 | - author=tkf 41 | - label=ready-to-merge:rebase 42 | - label!=work-in-progress 43 | actions: 44 | merge: 45 | method: rebase 46 | - name: automatic merge when CI passes 47 | conditions: 48 | - base=master 49 | - "#approved-reviews-by>=1" 50 | - label=ready-to-merge:merge 51 | - label!=work-in-progress 52 | actions: 53 | merge: 54 | method: merge 55 | - name: automatic merge when CI passes (@tkf) 56 | conditions: 57 | - base=master 58 | - author=tkf 59 | - label=ready-to-merge:merge 60 | - label!=work-in-progress 61 | actions: 62 | merge: 63 | method: merge 64 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: serve test clean instantiate 2 | 3 | JULIA_CMD = julia --color=yes --startup-file=no 4 | 5 | serve: instantiate 6 | bin/serve 7 | 8 | test: instantiate 9 | bin/runtests.jl 10 | 11 | clean: 12 | rm -rf src/__site 13 | 14 | instantiate: 15 | $(JULIA_CMD) -e 'using Pkg; Pkg.activate("."); Pkg.instantiate()' 16 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | BangBang = "198e06fe-97b7-11e9-32a5-e1d131e6ad66" 3 | Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" 4 | FLoops = "cc61a311-1640-44b5-9fba-1b764f453329" 5 | Folds = "41a02a25-b8f0-4f67-bc48-60067656b558" 6 | Franklin = "713c75ef-9fc9-4b05-94a9-213340da978e" 7 | Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" 8 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 9 | MicroCollections = "128add7d-3638-4c79-886c-908ea0c25c34" 10 | NodeJS = "2bd173c7-0d6d-553b-b6af-13a54713934c" 11 | OnlineStats = "a15396b6-48d5-5d58-9928-6d29437db91e" 12 | Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" 13 | ThreadsX = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d" 14 | Transducers = "28d57a85-8fef-5791-bfe6-a80928e7c999" 15 | UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data-parallel Julia 2 | 3 | This is the code repository of 4 | [A quick introduction to data parallelism in Julia](https://juliafolds.github.io/data-parallelism/tutorials/quick-introduction/). 5 | -------------------------------------------------------------------------------- /bin/runtests.jl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- mode: julia -*- 3 | #= 4 | JULIA="${JULIA:-julia --color=yes --startup-file=no}" 5 | export JULIA_PROJECT="$(dirname "$(dirname "${BASH_SOURCE[0]}")")" 6 | export JULIA_NUM_THREADS=4 7 | export DISPLAY= 8 | export GKSwstype=nul 9 | exec ${JULIA} "${BASH_SOURCE[0]}" "$@" 10 | =# 11 | module TestDataParallelism 12 | 13 | using Test 14 | using Glob 15 | 16 | @testset "$(basename(file))" for file in sort( 17 | readdir(glob"*/*/test_*.jl", joinpath(@__DIR__, "../src/_assets/")), 18 | ) 19 | include(file) 20 | end 21 | 22 | end # module TestDataParallelism 23 | -------------------------------------------------------------------------------- /bin/serve: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- mode: julia -*- 3 | #= 4 | JULIA="${JULIA:-julia --color=yes --startup-file=no}" 5 | export JULIA_PROJECT="$(dirname "$(dirname "${BASH_SOURCE[0]}")")" 6 | export DISPLAY= 7 | export GKSwstype=nul 8 | exec ${JULIA} "${BASH_SOURCE[0]}" "$@" 9 | =# 10 | cd(joinpath(dirname(@__DIR__), "src")) 11 | using Franklin 12 | serve(port=8643) 13 | -------------------------------------------------------------------------------- /scripts/collatz.jl: -------------------------------------------------------------------------------- 1 | # Load functions related to Collatz examples 2 | include("../src/__site/assets/tutorials/quick-introduction/code/def_collatz.jl") 3 | include("../src/__site/assets/tutorials/quick-introduction/code/def_collatz_stopping_time.jl") 4 | include("../src/__site/assets/tutorials/quick-introduction/code/hist_collatz_stopping_time.jl") 5 | -------------------------------------------------------------------------------- /scripts/false_sharing_demo.jl: -------------------------------------------------------------------------------- 1 | using BenchmarkTools 2 | 3 | function crowded_inc!(ys, data) 4 | Threads.@threads :static for indices in data 5 | for i in indices 6 | @inbounds ys[i] += 1 7 | end 8 | end 9 | end 10 | 11 | function exclusive_inc!(yss, data) 12 | Threads.@threads :static for indices in data 13 | ys = yss[Threads.threadid()] 14 | for i in indices 15 | @inbounds ys[i] += 1 16 | end 17 | end 18 | end 19 | 20 | cacheline = try 21 | parse(Int, read("/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size", String)) 22 | catch err 23 | @warn "cannot read cache line size" exception = (err, catch_backtrace()) 24 | 64 25 | end 26 | 27 | ys = zeros(Threads.nthreads() * 2); 28 | partitioned_indices = reshape(eachindex(ys), Threads.nthreads(), :)' 29 | data = [rand(partitioned_indices[:, i], 2^20) for i in 1:Threads.nthreads()] 30 | yss = [zeros(length(ys) + cld(cacheline, sizeof(eltype(ys)))) for _ in 1:Threads.nthreads()]; 31 | 32 | 33 | trial_crowded_inc = @benchmark crowded_inc!(ys, data) setup = fill!(ys, 0) 34 | display(trial_crowded_inc) 35 | trial_exclusive_inc = 36 | @benchmark exclusive_inc!(yss, data) setup = foreach(ys -> fill(ys, 0), yss) 37 | display(trial_exclusive_inc) 38 | -------------------------------------------------------------------------------- /scripts/perf_c2c_demo.jl: -------------------------------------------------------------------------------- 1 | # A demo for false sharing and its analysis using `perf c2c`. 2 | # 3 | # This script runs two Julia functions that does equivalent computations with 4 | # different memory layouts. The performance counters are analyzed using `perf 5 | # c2c` and the result files are dumped into the current working directory. 6 | # 7 | # This script works probably only on Intel CPU. 8 | 9 | if !success(`perf c2c record -- --output=/dev/null true`) 10 | error("`perf c2c` not supported") 11 | end 12 | 13 | using BenchmarkTools 14 | 15 | function perf_c2c_record(f, output) 16 | # Compile `f` and also (hopefully) let CPUs converge to a stationary state. 17 | f() 18 | 19 | proc = run( 20 | pipeline(`perf c2c record -- --output=$output`; stdout = stdout, stderr = stderr); 21 | wait = false, 22 | ) 23 | try 24 | f() 25 | finally 26 | flush(stdout) 27 | flush(stderr) 28 | kill(proc, Base.SIGINT) 29 | wait(proc) 30 | end 31 | end 32 | 33 | function perf_c2c_report(input, output) 34 | # `-c tid,iaddr` for showing the Tid column 35 | cmd = `perf c2c report --input=$input -c tid,iaddr` 36 | @info "$cmd > $output" 37 | open(output; write = true) do io 38 | run(pipeline(cmd; stdout = io)) 39 | end 40 | end 41 | 42 | # SYS_gettid == 186 from /usr/include/x86_64-linux-gnu/asm/unistd_64.h 43 | gettid() = @ccall syscall(186::Clong;)::Clong 44 | @assert gettid() == getpid() 45 | 46 | function worker_tids() 47 | tids = zeros(Int, Threads.nthreads()) 48 | Threads.@threads :static for _ in 1:Threads.nthreads() 49 | tids[Threads.threadid()] = gettid() 50 | end 51 | return tids 52 | end 53 | 54 | function crowded_inc!(ys, data) 55 | Threads.@threads :static for indices in data 56 | for i in indices 57 | @inbounds ys[i] += 1 58 | end 59 | end 60 | end 61 | 62 | function exclusive_inc!(yss, data) 63 | Threads.@threads :static for indices in data 64 | ys = yss[Threads.threadid()] 65 | for i in indices 66 | @inbounds ys[i] += 1 67 | end 68 | end 69 | end 70 | 71 | cacheline = try 72 | parse(Int, read("/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size", String)) 73 | catch err 74 | @warn "cannot read cache line size" exception = (err, catch_backtrace()) 75 | 64 76 | end 77 | 78 | ys = zeros(Threads.nthreads() * 2); 79 | partitioned_indices = reshape(eachindex(ys), Threads.nthreads(), :)' 80 | data = [rand(partitioned_indices[:, i], 2^20) for i in 1:Threads.nthreads()] 81 | yss = [zeros(length(ys) + cld(cacheline, sizeof(eltype(ys)))) for _ in 1:Threads.nthreads()]; 82 | 83 | 84 | @noinline bench_crowded_inc() = @btime crowded_inc!(ys, data) 85 | @noinline bench_exclusive_inc() = @btime exclusive_inc!(yss, data) 86 | 87 | 88 | @info "Benchmarking `crowded_inc!`" 89 | perf_c2c_record(bench_crowded_inc, "crowded_inc-perf.data") 90 | 91 | @info "Benchmarking `exclusive_inc!`" 92 | perf_c2c_record(bench_exclusive_inc, "exclusive_inc-perf.data") 93 | 94 | 95 | open("pointers.txt"; write = true) do io 96 | function ln(label, ptr::Ptr) 97 | print(io, label, ",\t") 98 | show(io, UInt(ptr)) 99 | println(io) 100 | end 101 | ln("ys[1]", pointer(ys, 1)) 102 | ln("ys[end]", pointer(ys, length(ys))) 103 | for (i, ys) in pairs(yss) 104 | ln("yss[$i][1]", pointer(ys, 1)) 105 | ln("yss[$i][end]", pointer(ys, length(ys))) 106 | end 107 | end 108 | 109 | open("worker_tids.txt"; write = true) do io 110 | for tid in worker_tids() 111 | println(io, tid) 112 | end 113 | end 114 | 115 | perf_c2c_report("crowded_inc-perf.data", "crowded_inc-perf.txt") 116 | perf_c2c_report("exclusive_inc-perf.data", "exclusive_inc-perf.txt") 117 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .test-result 3 | __site/ 4 | _rss 5 | franklin 6 | franklin.pub 7 | node_modules/ 8 | -------------------------------------------------------------------------------- /src/404.md: -------------------------------------------------------------------------------- 1 | # 404: File not found 2 | 3 | The requested file was not found. 4 | 5 | Please [click here](/) to go to the home page. 6 | -------------------------------------------------------------------------------- /src/_assets/hamburger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_assets/juliafolds-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuliaFolds/data-parallelism/0d15d8878d814b3988dbf0a4434cc036c4e20a9b/src/_assets/juliafolds-logo.ico -------------------------------------------------------------------------------- /src/_assets/rndimg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuliaFolds/data-parallelism/0d15d8878d814b3988dbf0a4434cc036c4e20a9b/src/_assets/rndimg.jpg -------------------------------------------------------------------------------- /src/_assets/scripts/generate_results.jl: -------------------------------------------------------------------------------- 1 | # Parent file to run all scripts which may generate 2 | # some output that you want to display on the website. 3 | # this can be used as a tester to check that all the code 4 | # on your website runs properly. 5 | 6 | dir = @__DIR__ 7 | 8 | """ 9 | genplain(s) 10 | 11 | Small helper function to run some code and redirect the output (stdout) to a file. 12 | """ 13 | function genplain(s::String) 14 | open(joinpath(dir, "output", "$(splitext(s)[1]).txt"), "w") do outf 15 | redirect_stdout(outf) do 16 | include(joinpath(dir, s)) 17 | end 18 | end 19 | end 20 | 21 | # output 22 | 23 | genplain("script1.jl") 24 | 25 | # plots 26 | 27 | include("script2.jl") 28 | -------------------------------------------------------------------------------- /src/_assets/scripts/output/script1.out: -------------------------------------------------------------------------------- 1 | *---------1.3 2 | **--------1.3 3 | ***-------1.3 4 | ****------1.3 5 | *****-----1.3 6 | -------------------------------------------------------------------------------- /src/_assets/scripts/output/script2.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 553 | 554 | 555 | 558 | 559 | 560 | 563 | 564 | 565 | 568 | 569 | 570 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | -------------------------------------------------------------------------------- /src/_assets/scripts/script1.jl: -------------------------------------------------------------------------------- 1 | using LinearAlgebra # HIDE 2 | using Random:seed! # HIDE 3 | seed!(0) # HIDE 4 | # HIDE 5 | x = randn(5) 6 | y = randn(5) 7 | 8 | for i in 1:5 9 | println(rpad("*"^i, 10, '-'), round(dot(x, y), digits=1)) 10 | end 11 | -------------------------------------------------------------------------------- /src/_assets/scripts/script2.jl: -------------------------------------------------------------------------------- 1 | using PyPlot 2 | x = range(0, stop=1, length=50) 3 | plot(x, sin.(2x).*exp.(-x/3)) 4 | savefig(joinpath(@__DIR__, "output", "script2.svg")) 5 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/accidental_mutations_1_threads.jl: -------------------------------------------------------------------------------- 1 | function f(n = 2^10) 2 | ys = zeros(Int, n) 3 | Threads.@threads for i in 1:n 4 | y = gcd(42, i) 5 | some_function() 6 | ys[i] = y 7 | end 8 | 9 | # Suppose that some unrelated code uses the same variable names as the 10 | # temporary variables in the parallel loop: 11 | if ys[1] > 0 12 | y = 1 13 | end 14 | 15 | return ys 16 | end 17 | 18 | # Some function that Julia does not inline: 19 | @noinline some_function() = _FALSE_[] && error("unreachable") 20 | const _FALSE_ = Ref(false) 21 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/accidental_mutations_2_seq.jl: -------------------------------------------------------------------------------- 1 | f_seq(n = 2^10) = gcd.(42, 1:n) 2 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/accidental_mutations_3_threads_fixed.jl: -------------------------------------------------------------------------------- 1 | function f_fixed(n = 2^10) 2 | ys = zeros(Int, n) 3 | Threads.@threads for i in 1:n 4 | local y = gcd(42, i) 5 | some_function() 6 | ys[i] = y 7 | end 8 | 9 | # Suppose that some unrelated code uses the same variable names as the 10 | # temporary variables in the parallel loop: 11 | if ys[1] > 0 12 | y = 1 13 | end 14 | 15 | return ys 16 | end 17 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/accidental_mutations_4_floop.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | function f_floop(n = 2^10) 4 | ys = zeros(Int, n) 5 | @floop ThreadedEx() for i in 1:n 6 | y = gcd(42, i) 7 | some_function() 8 | ys[i] = y 9 | end 10 | 11 | if ys[1] > 0 12 | y = 1 13 | end 14 | 15 | return ys 16 | end 17 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/accidental_mutations_5_floop_fixed.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | function f_floop_fixed(n = 2^10) 4 | ys = zeros(Int, n) 5 | @floop ThreadedEx() for i in 1:n 6 | local y = gcd(42, i) 7 | some_function() 8 | ys[i] = y 9 | end 10 | 11 | if ys[1] > 0 12 | y = 1 13 | end 14 | 15 | return ys 16 | end 17 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/adjoining_trick_1.jl: -------------------------------------------------------------------------------- 1 | struct Init end 2 | 3 | function asmonoid(semigroup) 4 | monoid(a, b) = semigroup(a, b) 5 | monoid(::Init, b) = b 6 | monoid(a, ::Init) = a 7 | monoid(::Init, ::Init) = Init() # disambiguation 8 | return monoid 9 | end 10 | 11 | let ⊗ = asmonoid(min) 12 | @assert 1 ⊗ 2 == 1 13 | @assert 1 ⊗ Init() == 1 14 | @assert Init() ⊗ 2 == 2 15 | @assert Init() ⊗ Init() == Init() 16 | end 17 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/adjoining_trick_2.jl: -------------------------------------------------------------------------------- 1 | function withinit(f, semigroup!) 2 | monoid!(a, b) = semigroup!(a, b) 3 | monoid!(::Init, b) = semigroup!(f(), b) 4 | monoid!(a, ::Init) = a 5 | monoid!(::Init, ::Init) = Init() # disambiguation 6 | return monoid! 7 | end 8 | 9 | let ⊗ = withinit(() -> [], append!) 10 | @assert [1] ⊗ [2] == [1, 2] 11 | @assert [1] ⊗ Init() == [1] 12 | @assert Init() ⊗ [2] == [2] 13 | @assert Init() ⊗ Init() == Init() 14 | end 15 | 16 | using Folds 17 | ys = Folds.mapreduce(tuple, withinit(() -> Int[], append!), 1:10; init = Init()) 18 | 19 | @assert ys == 1:10 20 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/combining_containers_1.jl: -------------------------------------------------------------------------------- 1 | using Folds 2 | using Transducers: OnInit 3 | ys1 = Folds.mapreduce(x -> [x], append!, 1:10; init = OnInit(() -> Int[])) 4 | ys2 = Folds.mapreduce(x -> Set([x]), union!, 1:10; init = OnInit(Set{Int})) 5 | ys3 = Folds.mapreduce(x -> Dict(x => x^2), merge!, 1:10; init = OnInit(Dict{Int,Int})) 6 | ys4 = Folds.mapreduce(x -> Dict(isodd(x) => 1), mergewith!(+), 1:10; init = OnInit(Dict{Bool,Int})) 7 | 8 | @assert ys1 == 1:10 9 | @assert ys2 == Set(1:10) 10 | @assert ys3 == Dict(x => x^2 for x in 1:10) 11 | @assert ys4 == Dict(false => 5, true => 5) 12 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/combining_containers_2.jl: -------------------------------------------------------------------------------- 1 | using Folds 2 | using MicroCollections 3 | using BangBang 4 | ys1 = Folds.mapreduce(x -> SingletonVector((x,)), append!!, 1:10) 5 | ys2 = Folds.mapreduce(x -> SingletonSet((x,)), union!!, 1:10) 6 | ys3 = Folds.mapreduce(x -> SingletonDict(x => x^2), merge!!, 1:10) 7 | ys4 = Folds.mapreduce(x -> SingletonDict(isodd(x) => 1), mergewith!!(+), 1:10) 8 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/combining_containers_3.jl: -------------------------------------------------------------------------------- 1 | using Folds 2 | ys1 = Folds.collect(1:10) 3 | ys2 = Folds.set(1:10) 4 | ys3 = Folds.dict(x => x^2 for x in 1:10) 5 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/example_1.jl: -------------------------------------------------------------------------------- 1 | N = 40 2 | M = 1000 3 | As = [randn(N, N) for _ in 1:M] 4 | Bs = [randn(N, N) for _ in 1:M] 5 | sum(A * B for (A, B) in zip(As, Bs)) 6 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/example_2.jl: -------------------------------------------------------------------------------- 1 | using Folds 2 | Folds.sum(A * B for (A, B) in zip(As, Bs)) 3 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/example_3.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | @floop for (A, B) in zip(As, Bs) 4 | C = A * B # allocation for each iteration 5 | @reduce() do (S = zero(C); C) 6 | S = S + C # allocation for each iteration 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/example_4.jl: -------------------------------------------------------------------------------- 1 | using LinearAlgebra: mul! 2 | 3 | @floop for (A, B) in zip(As, Bs) 4 | @init C = similar(A) 5 | mul!(C, A, B) 6 | @reduce() do (S = zero(C); C) 7 | S .+= C 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/example_5.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | using LinearAlgebra: mul! 3 | 4 | @floop for (A, B) in zip(As, Bs) 5 | C = (A, B) 6 | @reduce() do (S = zero(A); C) 7 | if C isa Tuple # base case 8 | mul!(S, C[1], C[2], 1, 1) 9 | else # combining base cases 10 | S .+= C 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/filling_1.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | xs = 1:2:100 4 | ys = similar(xs) # output 5 | @floop ThreadedEx() for (i, x) in pairs(xs) 6 | @inbounds ys[i] = gcd(42, x) 7 | end 8 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/filling_2.jl: -------------------------------------------------------------------------------- 1 | xs = 1:2:100 2 | ys = similar(xs) # output 3 | Threads.@threads for i in eachindex(xs, ys) 4 | @inbounds ys[i] = gcd(42, xs[i]) 5 | end 6 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/filling_3.jl: -------------------------------------------------------------------------------- 1 | using Folds 2 | 3 | xs = 1:2:100 4 | ys = similar(xs) # output 5 | Folds.map!(x -> gcd(42, x), ys, xs) 6 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_init_1.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | @floop for x in 1:10 4 | @init xs = Vector{Int}(undef, 3) 5 | xs .= (x, 2x, 3x) 6 | @reduce() do (ys = zeros(Int, 3); xs) 7 | ys .+= xs 8 | end 9 | end 10 | 11 | @assert ys == mapreduce(x -> [x, 2x, 3x], .+, 1:10) 12 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_1.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | @floop for x in 1:10 4 | if isodd(x) 5 | @reduce(odds = append!(Int[], (x,))) 6 | else 7 | @reduce(evens = append!(Int[], (x,))) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_2.jl: -------------------------------------------------------------------------------- 1 | function basecase(chunk) 2 | odds = Int[] # init 3 | evens = Int[] # init 4 | for x in chunk 5 | if isodd(x) 6 | odds = append!(odds, (x,)) 7 | else 8 | evens = append!(evens, (x,)) 9 | end 10 | end 11 | return (odds, evens) 12 | end 13 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_3.jl: -------------------------------------------------------------------------------- 1 | chunk_left = 1:5 2 | chunk_right = 6:10 3 | @assert vcat(chunk_left, chunk_right) == 1:10 # original input 4 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_4.jl: -------------------------------------------------------------------------------- 1 | odds_left, evens_left = basecase(chunk_left) 2 | odds_right, evens_right = basecase(chunk_right) 3 | 4 | @assert odds_left == 1:2:5 5 | @assert evens_left == 2:2:5 6 | @assert odds_right == 7:2:10 7 | @assert evens_right == 6:2:10 8 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_5.jl: -------------------------------------------------------------------------------- 1 | odds = append!(odds_left, odds_right) 2 | evens = append!(evens_left, evens_right) 3 | 4 | @assert odds == 1:2:10 5 | @assert evens == 2:2:10 6 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_6_vcat.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | 3 | @floop for x in 1:10 4 | if isodd(x) 5 | @reduce(odds = vcat(Int[], [x])) 6 | else 7 | @reduce(evens = vcat(Int[], [x])) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_1.jl: -------------------------------------------------------------------------------- 1 | @floop for n in 1:10 2 | xs = [n, 2n, 3n] 3 | @reduce() do (ys = zeros(Int, 3); xs) 4 | # ~~~~~~~~~~~~~~~~~~ 5 | # initializer 6 | ys .+= xs 7 | # ~~~~~~~~~ 8 | # reduce body 9 | end 10 | end 11 | 12 | @assert ys == mapreduce(n -> [n, 2n, 3n], .+, 1:10) 13 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_2.jl: -------------------------------------------------------------------------------- 1 | function basecase(chunk) 2 | ys = zeros(Int, 3) # initializer 3 | for n in chunk 4 | xs = [n, 2n, 3n] 5 | ys .+= xs # reduce body 6 | end 7 | return ys 8 | end 9 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_3.jl: -------------------------------------------------------------------------------- 1 | chunk_left = 1:5 2 | chunk_right = 6:10 3 | 4 | ys_left = basecase(chunk_left) 5 | ys_right = basecase(chunk_right) 6 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_4.jl: -------------------------------------------------------------------------------- 1 | ys_left .+= ys_right 2 | ys = ys_left 3 | 4 | @assert ys == mapreduce(n -> [n, 2n, 3n], .+, 1:10) 5 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_correct.jl: -------------------------------------------------------------------------------- 1 | @floop for n in 1:10 2 | xs = [n, 2n, 3n] 3 | zs = 2 .* xs # CORRECT 4 | @reduce() do (ys = zeros(Int, 3); zs) 5 | ys .+= zs 6 | end 7 | end 8 | 9 | @assert ys == mapreduce(n -> 2 .* [n, 2n, 3n], .+, 1:10) 10 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_incorrect.jl: -------------------------------------------------------------------------------- 1 | @floop for n in 1:10 2 | xs = [n, 2n, 3n] 3 | @reduce() do (ys = zeros(Int, 3); xs) 4 | ys .+= 2 .* xs # INCORRECT 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_os_1.jl: -------------------------------------------------------------------------------- 1 | using FLoops 2 | using OnlineStats 3 | 4 | @floop for x in 1:10 5 | y = 2x 6 | m = fit!(Mean(), y) 7 | @reduce() do (acc = Mean(); m) 8 | merge!(acc, m) 9 | end 10 | end 11 | 12 | @assert acc == fit!(Mean(), 2:2:20) 13 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/floops_reduce_do_os_2.jl: -------------------------------------------------------------------------------- 1 | @floop for x in 1:10 2 | y = 2x 3 | @reduce() do (acc = Mean(); y) 4 | if y isa OnlineStat 5 | merge!(acc, y) 6 | else 7 | fit!(acc, y) 8 | end 9 | end 10 | end 11 | 12 | @assert acc == fit!(Mean(), 2:2:20) 13 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/ownership_passing_style_second_1_intro.jl: -------------------------------------------------------------------------------- 1 | vectors = Any[[n n^2; n^3 n^4] for n in 1:100] 2 | sum(vectors) 3 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/ownership_passing_style_second_2_wrong.jl: -------------------------------------------------------------------------------- 1 | vectors = Any[[n n^2; n^3 n^4] for n in 1:100] 2 | @floop for xs in vectors 3 | @reduce() do (ys = nothing; xs) 4 | if ys === nothing 5 | ys = xs # ❌ WRONG 6 | else 7 | ys .+= xs 8 | end 9 | end 10 | end 11 | 12 | @assert ys === vectors[1] # above loop mutated the input 13 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/ownership_passing_style_second_3_correct.jl: -------------------------------------------------------------------------------- 1 | vectors = Any[[n n^2; n^3 n^4] for n in 1:100] 2 | @floop for xs in vectors 3 | @reduce() do (ys = nothing; xs) 4 | if ys === nothing 5 | ys = copy(xs) # ✅ CORRECT 6 | else 7 | ys .+= xs 8 | end 9 | end 10 | end 11 | 12 | @assert ys !== vectors[1] # input not mutated 13 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/test_mutations.jl: -------------------------------------------------------------------------------- 1 | module TestMutations 2 | 3 | using Test 4 | 5 | module Example 6 | S1 = include("example_1.jl") 7 | S2 = include("example_2.jl") 8 | include("example_3.jl") 9 | S3 = S 10 | S = nothing 11 | include("example_4.jl") 12 | S4 = S 13 | S = nothing 14 | include("example_5.jl") 15 | S5 = S 16 | end # module Example 17 | 18 | @testset "Example" begin 19 | @test Example.S2 ≈ Example.S1 20 | @test Example.S3 ≈ Example.S1 21 | @test Example.S4 ≈ Example.S1 22 | @test Example.S5 ≈ Example.S1 23 | end 24 | 25 | module Filling 26 | include("filling_1.jl") 27 | ys1 = ys 28 | ys = nothing 29 | include("filling_2.jl") 30 | ys2 = ys 31 | ys = nothing 32 | ys3 = include("filling_3.jl") 33 | end 34 | 35 | @testset "Filling" begin 36 | @test Filling.ys1 == gcd.(42, 1:2:100) 37 | @test Filling.ys2 == gcd.(42, 1:2:100) 38 | @test Filling.ys3 == gcd.(42, 1:2:100) 39 | end 40 | 41 | module FLoopsReduce 42 | using Test: @test as @assert 43 | include("floops_reduce_1.jl") 44 | include("floops_reduce_2.jl") 45 | include("floops_reduce_3.jl") 46 | include("floops_reduce_4.jl") 47 | include("floops_reduce_5.jl") 48 | end 49 | 50 | module FLoopsReduce6 51 | include("floops_reduce_6_vcat.jl") 52 | end 53 | 54 | @testset "floops_reduce_6_vcat.jl" begin 55 | @test FLoopsReduce6.odds == 1:2:10 56 | @test FLoopsReduce6.evens == 2:2:10 57 | end 58 | 59 | module FLoopsReduceDo 60 | using Test: @test as @assert 61 | using FLoops 62 | include("floops_reduce_do_1.jl") 63 | include("floops_reduce_do_2.jl") 64 | include("floops_reduce_do_3.jl") 65 | include("floops_reduce_do_4.jl") 66 | end 67 | 68 | module FLoopsReduceDoOnlineStats 69 | using Test: @test as @assert 70 | include("floops_reduce_do_os_1.jl") 71 | acc = nothing 72 | include("floops_reduce_do_os_2.jl") 73 | end 74 | 75 | module FLoopsReduceDo2 76 | using Test: @test as @assert 77 | using FLoops 78 | include("floops_reduce_do_incorrect.jl") 79 | include("floops_reduce_do_correct.jl") 80 | end 81 | 82 | module OwnershipPassingStyleSecond1 83 | include("ownership_passing_style_second_1_intro.jl") 84 | end 85 | 86 | module OwnershipPassingStyleSecond2 87 | using Test: @test as @assert 88 | using FLoops 89 | include("ownership_passing_style_second_2_wrong.jl") 90 | end 91 | 92 | module OwnershipPassingStyleSecond3 93 | using Test: @test as @assert 94 | using FLoops 95 | include("ownership_passing_style_second_3_correct.jl") 96 | end 97 | 98 | module TransducersOnInit 99 | using Test: @test as @assert 100 | include("transducers_oninit.jl") 101 | end 102 | 103 | module CombiningContainers1 104 | using Test: @test as @assert 105 | include("combining_containers_1.jl") 106 | end 107 | 108 | module CombiningContainers2 109 | include("combining_containers_2.jl") 110 | end 111 | 112 | module CombiningContainers3 113 | include("combining_containers_3.jl") 114 | end 115 | 116 | @testset "CombiningContainers" begin 117 | @test CombiningContainers2.ys1 == CombiningContainers1.ys1 118 | @test CombiningContainers2.ys2 == CombiningContainers1.ys2 119 | @test CombiningContainers2.ys3 == CombiningContainers1.ys3 120 | @test CombiningContainers2.ys4 == CombiningContainers1.ys4 121 | @test CombiningContainers3.ys1 == CombiningContainers1.ys1 122 | @test CombiningContainers3.ys2 == CombiningContainers1.ys2 123 | @test CombiningContainers3.ys3 == CombiningContainers1.ys3 124 | end 125 | 126 | module AccidentalMutations 127 | include("accidental_mutations_1_threads.jl") 128 | include("accidental_mutations_2_seq.jl") 129 | include("accidental_mutations_3_threads_fixed.jl") 130 | include("accidental_mutations_4_floop.jl") 131 | include("accidental_mutations_5_floop_fixed.jl") 132 | end 133 | 134 | @testset "AccidentalMutations" begin 135 | @test AccidentalMutations.f() isa Any 136 | @test AccidentalMutations.f_fixed() == AccidentalMutations.f_seq() 137 | @test AccidentalMutations.f_floop_fixed() == AccidentalMutations.f_seq() 138 | 139 | local err 140 | @test (err = try 141 | AccidentalMutations.f_floop() 142 | nothing 143 | catch err_ 144 | err_ 145 | end) isa Exception 146 | @test occursin("has 1 boxed variable", sprint(showerror, err)) 147 | end 148 | 149 | module FLoopsInit 150 | using Test: @test as @assert 151 | include("floops_init_1.jl") 152 | end 153 | 154 | module AdjoiningTrick 155 | using Test: @test as @assert 156 | include("adjoining_trick_1.jl") 157 | include("adjoining_trick_2.jl") 158 | end 159 | 160 | end # module 161 | -------------------------------------------------------------------------------- /src/_assets/tutorials/mutations/transducers_oninit.jl: -------------------------------------------------------------------------------- 1 | using Folds 2 | using Transducers: OnInit 3 | ys = Folds.mapreduce(x -> (x, 2x, 3x), .+, 1:10; init = OnInit(() -> [0, 0, 0])) 4 | 5 | @assert ys == [sum(1:10), 2sum(1:10), 3sum(1:10)] 6 | -------------------------------------------------------------------------------- /src/_css/basic.css: -------------------------------------------------------------------------------- 1 | /* ================================================================== 2 | Header and Nav 3 | ================================================================== */ 4 | 5 | nav { 6 | width: 64%; 7 | display: inline-block; } 8 | 9 | nav ul { 10 | padding-left: 0; 11 | margin-top: 0; 12 | margin-bottom: 0; } 13 | 14 | nav li { 15 | display: inline-block; } 16 | 17 | nav li a { 18 | color: #004de6; 19 | text-decoration: none; 20 | font-size: 18px; 21 | font-weight: bold; 22 | display: inline-block; 23 | float: center; 24 | padding-top: 10px; 25 | padding-right:2px; 26 | padding-left:2px; 27 | padding-bottom:5px; 28 | margin-left:7px; 29 | margin-right:7px; 30 | border-bottom: 2px solid #4C9CF1; 31 | transition: color 0.3s ease; } 32 | 33 | header { 34 | text-align: right; 35 | padding-left: 30px; 36 | padding-right: 30px; 37 | margin-top: 50px; 38 | margin-bottom: 50px; 39 | display: flex; 40 | align-items: center; } 41 | 42 | header .blog-name { 43 | width: 35%; 44 | display: inline-block; 45 | text-align: left; 46 | font-size: 18px; 47 | font-family: "Lucida Console", Monaco, monospace; 48 | padding-top: 10px;} 49 | 50 | header .blog-name a { 51 | color: #a6a2a0; 52 | text-decoration: none; } 53 | 54 | header li a:hover { 55 | color: black; 56 | border-bottom: 2px solid black; } 57 | 58 | 59 | #menu-icon { 60 | display: none; } 61 | 62 | .github-edit-small { 63 | display: none; } 64 | 65 | @media (max-width: 480px) { 66 | header { 67 | padding-left: 6%; 68 | padding-right:6%; 69 | } 70 | } 71 | 72 | /* wide display: enforce maximum width of header to match content */ 73 | @media (min-width: 940px) { 74 | header { 75 | margin-left: auto; 76 | margin-right: auto; } 77 | } 78 | 79 | /* 80 | medium display: nav goes under name 81 | */ 82 | @media (max-width: 760px) { 83 | header { display: block; } 84 | header .blog-name { 85 | display: block; 86 | width: 100%; 87 | padding-bottom: 10px; } 88 | nav { 89 | width: 100%; } 90 | } 91 | 92 | /* 93 | narrow display: collapse the header (don't show the menu items) 94 | instead, display a burger menu. 95 | */ 96 | @media (max-width: 500px) { 97 | header { 98 | height: 35px; 99 | display: flex; 100 | align-items: center; } 101 | header .blog-name { 102 | display: inline-block; 103 | width: 70%; } 104 | nav { 105 | display: inline-block; 106 | width: 27%; } 107 | nav ul, nav:active ul { 108 | display: none; 109 | position: absolute; 110 | /* padding: 20px; */ 111 | background: #fff; 112 | border: 1px solid #444; 113 | right: 50px; 114 | top: 60px; 115 | width: 30%; 116 | border-radius: 4px 0 4px 4px; 117 | z-index: 1;} 118 | nav li { 119 | text-align: left; 120 | display: block; 121 | padding: 0; 122 | margin: 0; } 123 | header li a { border-bottom: none; } 124 | header li a:hover { border-bottom: none; } 125 | nav:hover ul{ 126 | display: block; } 127 | #menu-icon { 128 | display: inline-block; 129 | margin-right: 10px; 130 | margin-top: 5px; } 131 | .github-edit-big { 132 | display: none; } 133 | .github-edit-small { 134 | display: inline-block; } 135 | } 136 | 137 | table { 138 | line-height:1em; 139 | margin-left:auto; 140 | margin-right:auto; 141 | border-collapse:collapse; 142 | text-align:center; 143 | margin-bottom:1.5em 144 | } 145 | tr:first-of-type { 146 | background:#eae9f4 147 | } 148 | tr:first-of-type>th { 149 | text-align:center 150 | } 151 | tr, 152 | th, 153 | td { 154 | padding:10px; 155 | border:1px solid lightgray 156 | } 157 | table tbody tr td { 158 | border:1px solid lightgray 159 | } 160 | -------------------------------------------------------------------------------- /src/_css/franklin.css: -------------------------------------------------------------------------------- 1 | /* ================================================================== 2 | DEFAULT FONT AND LAYOUT 3 | ================================================================== */ 4 | 5 | html { 6 | font-family: Helvetica, Arial, sans-serif; 7 | font-size : 17px; 8 | color: #1c1c1c; 9 | } 10 | 11 | /* ================================================================== 12 | BASIC GRID FOR PROFILE PIC 13 | ================================================================== */ 14 | 15 | .franklin-content .row { 16 | display: block; } 17 | 18 | .franklin-content .left { 19 | float: left; 20 | margin-right: 15px; } 21 | 22 | .franklin-content .right { 23 | float: right; } 24 | 25 | .franklin-content .container img { 26 | width: auto; 27 | padding-left: 0; 28 | border-radius: 10px; } 29 | 30 | .franklin-content .footnote { 31 | position: relative; 32 | top: -0.5em; 33 | font-size: 70%; 34 | } 35 | 36 | /* ================================================================== 37 | FOOT / COPYRIGHT 38 | ================================================================== */ 39 | 40 | .franklin-content .page-foot a { 41 | text-decoration: none; 42 | color: #a6a2a0; 43 | text-decoration: underline; } 44 | 45 | .page-foot { 46 | font-size: 80%; 47 | font-family: Arial, serif; 48 | color: #a6a2a0; 49 | text-align: center; 50 | margin-top: 6em; 51 | border-top: 1px solid lightgrey; 52 | padding-top: 2em; 53 | margin-bottom: 4em; } 54 | 55 | /* ================================================================== 56 | TEXT GEOMETRY 57 | ================================================================== */ 58 | 59 | .franklin-content { 60 | position: relative; 61 | padding-left: 30px; 62 | padding-right: 30px; 63 | line-height: 1.35em; } 64 | 65 | /* on wide screens, fix content width to a max value */ 66 | @media (min-width: 940px) { 67 | .franklin-content { 68 | margin-left: auto; 69 | margin-right: auto; } 70 | } 71 | 72 | /* on narrow device, reduce margins */ 73 | @media (max-width: 480px) { 74 | .franklin-content { 75 | padding-left: 6%; 76 | padding-right: 6%; } 77 | } 78 | 79 | /* ================================================================== 80 | TITLES 81 | ================================================================== */ 82 | 83 | .franklin-content h1 { font-size: 24px; } 84 | .franklin-content h2 { font-size: 22px; } 85 | .franklin-content h3 { font-size: 20px; } 86 | 87 | .franklin-content h1, h2, h3, h4, h5, h6 { 88 | text-align: left; } 89 | 90 | .franklin-content h1 { 91 | padding-bottom: 0.5em; 92 | border-bottom: 3px double lightgrey; 93 | margin-top: 1.5em; 94 | margin-bottom: 1em; } 95 | 96 | .franklin-content h2 { 97 | padding-bottom: 0.3em; 98 | border-bottom: 1px solid lightgrey; 99 | margin-top: 2em; 100 | margin-bottom: 1em; } 101 | 102 | .franklin-content h1 a { color: inherit; } 103 | .franklin-content h1 a:hover {text-decoration: none;} 104 | .franklin-content h2 a { color: inherit; } 105 | .franklin-content h2 a:hover {text-decoration: none;} 106 | .franklin-content h3 a { color: inherit; } 107 | .franklin-content h3 a:hover {text-decoration: none;} 108 | .franklin-content h4 a { color: inherit; } 109 | .franklin-content h4 a:hover {text-decoration: none;} 110 | .franklin-content h5 a { color: inherit; } 111 | .franklin-content h5 a:hover {text-decoration: none;} 112 | .franklin-content h6 a { color: inherit; } 113 | .franklin-content h6 a:hover {text-decoration: none;} 114 | 115 | .franklin-content table { 116 | margin-left: auto; 117 | margin-right: auto; 118 | border-collapse: collapse; 119 | text-align: center;} 120 | .franklin-content th, td{ 121 | padding: 10px; 122 | border: 1px solid black;} 123 | 124 | .franklin-content blockquote { 125 | background: #eeeeee; 126 | border-left: 7px solid #a8a8a8; 127 | margin: 1.5em 10px; 128 | padding: 0.5em 10px; 129 | font-style: italic;} 130 | 131 | .franklin-content blockquote p { 132 | display: inline; } 133 | 134 | /* ================================================================== 135 | GENERAL FORMATTING 136 | ================================================================== */ 137 | 138 | /* spacing between bullet points */ 139 | .franklin-content li p { 140 | margin: 10px 0; } 141 | 142 | .franklin-content a { 143 | color: #004de6; 144 | text-decoration: none; } 145 | 146 | .franklin-content a:hover { 147 | text-decoration: underline; } 148 | 149 | /* ================================================================== 150 | HYPERREFS AND FOOTNOTES 151 | ================================================================== */ 152 | 153 | .franklin-content .eqref a { color: green; } 154 | .franklin-content .bibref a { color: green; } 155 | 156 | .franklin-content sup { 157 | font-size: 70%; 158 | vertical-align: super; 159 | line-height: 0; } 160 | 161 | .franklin-content table.fndef { 162 | margin: 0; 163 | margin-bottom: 10px;} 164 | .franklin-content .fndef tr, td { 165 | padding: 0; 166 | border: 0; 167 | text-align: left;} 168 | .franklin-content .fndef tr { 169 | border-left: 2px solid lightgray; 170 | } 171 | .franklin-content .fndef td.fndef-backref { 172 | vertical-align: top; 173 | font-size: 70%; 174 | padding-left: 5px;} 175 | .franklin-content .fndef td.fndef-content { 176 | font-size: 80%; 177 | padding-left: 10px;} 178 | 179 | /* ================================================================== 180 | IMAGES in CONTENT 181 | ================================================================== */ 182 | 183 | .franklin-content img { 184 | max-width: 70%; 185 | text-align: center; 186 | padding-left: 10%; } 187 | 188 | .franklin-content .img-small img { 189 | max-width: 50%; 190 | text-align: center; 191 | padding-left: 20%; } 192 | 193 | /* ================================================================== 194 | IMAGES in TABLE 195 | ================================================================== */ 196 | 197 | .franklin-content table img { 198 | max-width: 100%; 199 | text-align: left; 200 | padding-left: 0; } 201 | 202 | /* ================================================================== 203 | KATEX 204 | ================================================================== */ 205 | 206 | /* Franklin-specify setups are removed. */ 207 | 208 | /* ================================================================== 209 | CODE & HIGHLIGHT.JS 210 | ================================================================== */ 211 | 212 | code { 213 | background-color: rgba(27,31,35,0.05); 214 | padding: 0.1em 0.2em; 215 | border-radius: 2px; 216 | font-size: 90%; 217 | } 218 | 219 | /* .franklin-content code { */ 220 | /* background-color: rgba(27,31,35,0.05); */ 221 | /* padding: 0.1em 0.2em; */ 222 | /* border-radius: 2px; */ 223 | /* font-size: 90%; } */ 224 | 225 | .hljs { 226 | font-size: 90%; 227 | line-height: 1.35em; 228 | border-radius: 10px; } 229 | 230 | .hljs-meta, .hljs-metas, .hljs-metap { font-weight: bold; } 231 | 232 | .hljs-meta { color: rgb(25, 179, 51); } 233 | 234 | .hljs-metas { color: red; } 235 | 236 | .hljs-metap { color: rgb(51, 131, 231); } 237 | 238 | /* ================================================================== 239 | BOXES 240 | ================================================================== */ 241 | 242 | .franklin-content .colbox-blue { 243 | background-color: #EEF3F5; 244 | padding-top: 5px; 245 | padding-right: 10px; 246 | padding-left: 10px; 247 | padding-bottom: 5px; 248 | margin-left: 5px; 249 | margin-top: 5px; 250 | margin-bottom: 5px; 251 | border-radius: 0 10px 10px 0; 252 | border-left: 5px solid #4C9CF1; } 253 | 254 | /* ================================================================== 255 | ADMONITION 256 | ================================================================== */ 257 | 258 | /* Taken from: 259 | https://github.com/tlienart/Franklin.jl/blob/v0.10.3/docs/_css/jtd.css#L674-L696 260 | */ 261 | 262 | .note, .warn { 263 | margin-top: 1.5em; 264 | width: 95%; 265 | margin-left:auto; 266 | margin-right:auto; 267 | border-bottom-left-radius: 5px; 268 | border-bottom-right-radius: 5px; 269 | margin-bottom: 1em; } 270 | .note .content, 271 | .warn .content { 272 | padding: 10px; 273 | padding-left: 12px; } 274 | .note .title, 275 | .warn .title { 276 | font-size: 105%; 277 | border-top-left-radius: 5px; 278 | border-top-right-radius: 5px; 279 | padding-left: 7px; 280 | padding-top: 2px; } 281 | 282 | .note { 283 | background-color: aliceblue; } 284 | .note .title { 285 | color: white; 286 | background: cornflowerblue; } 287 | 288 | .warn { 289 | background-color: #f7e2e2; } 290 | .warn .title { 291 | color: white; 292 | background: #af4545; } 293 | 294 | /* ================================================================== 295 | TESTING 296 | ================================================================== */ 297 | 298 | .test { 299 | display: none; } 300 | 301 | .test_code > pre, 302 | .test_ok > pre, 303 | .test_failure > pre { 304 | margin-top: 0px; } 305 | 306 | .test_code > pre { 307 | margin-bottom: 5px; } 308 | 309 | .test_code > .title, 310 | .test_ok > .title, 311 | .test_failure > .title { 312 | font-family: monospace; 313 | font-size: 14px; 314 | font-weight: bold; } 315 | 316 | .test_ok > .title { 317 | color: green; } 318 | 319 | .test_failure > .title { 320 | color: red; } 321 | 322 | .test_code .hljs { 323 | background-color: lemonchiffon; } 324 | 325 | .test_ok .hljs { 326 | background-color: palegreen; } 327 | 328 | .test_failure .hljs { 329 | background-color: salmon; } 330 | 331 | /* ================================================================== 332 | MISC 333 | ================================================================== */ 334 | 335 | kbd { 336 | display: inline-block; 337 | padding: 3px 5px; 338 | font: 15px SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; 339 | line-height: 16px; 340 | color: #444d56; 341 | vertical-align: middle; 342 | background-color: #fafbfc; 343 | border: 1px solid #d1d5da; 344 | border-radius: 6px; 345 | box-shadow: inset 0 -1px 0 #d1d5da; } 346 | -------------------------------------------------------------------------------- /src/_layout/foot.html: -------------------------------------------------------------------------------- 1 | 2 | {{ if hasmath }} 3 | {{ insert foot_katex.html }} 4 | {{ end }} 5 | {{ if hascode }} 6 | {{ insert foot_highlight.html }} 7 | {{ end }} 8 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/_layout/foot_highlight.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/_layout/foot_katex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/_layout/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{if hasmath}} {{insert head_katex.html }} {{end}} 7 | {{if hascode}} {{insert head_highlight.html }} {{end}} 8 | 9 | 10 | 11 | {{isdef title}} {{fill title}} {{end}} 12 | 13 | 14 | {{insert header.html}} 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/_layout/head_highlight.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/_layout/head_katex.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/_layout/header.html: -------------------------------------------------------------------------------- 1 |
2 |
Data-parallel Julia
3 | 20 |
21 | -------------------------------------------------------------------------------- /src/_layout/page_foot.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | #show test results 4 | 5 |
6 |
7 | 10 | 13 |
14 | -------------------------------------------------------------------------------- /src/_layout/tag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tag: {{fill fd_tag}} 9 | 10 | 11 | {{insert header.html}} 12 |
13 |

Tag: {{fill fd_tag}}

14 | {{taglist}} 15 | {{insert page_foot.html}} 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/_libs/highlight/github.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:bold}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:#008080}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:bold}.hljs-subst{font-weight:normal}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:bold}.hljs-attribute,.hljs-name,.hljs-tag{color:#000080;font-weight:normal}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} 2 | -------------------------------------------------------------------------------- /src/_libs/highlight/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],i=Object.keys,_={},c={},C=!0,n=/^(no-?highlight|plain|text)$/i,l=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},o="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function g(e){return e.nodeName.toLowerCase()}function u(e){return n.test(e)}function s(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function E(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),g(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function d(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function R(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return s(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[s(n,{starts:n.starts?s(n.starts):null})]:Object.isFrozen(n)?[s(n)]:[n]}function p(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(p)}}function v(n,r){var a={};return"string"==typeof n?t("keyword",n):i(n).forEach(function(e){t(e,n[e])}),a;function t(t,e){r&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n=e.split("|");a[n[0]]=[t,function(e,n){return n?Number(n):function(e){return-1!=o.indexOf(e.toLowerCase())}(e)?0:1}(n[0],n[1])]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(n)+1}for(var r=0;r')+n+(t?"":m)}function l(){p+=null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=function(e){var n=e[0],t=i.substr(e.index),r=o(d,t);if(r){var a=d;for(a.skip?v+=n:(a.rE||a.eE||(v+=n),l(),a.eE&&(v=n));d.cN&&(p+=m),d.skip||d.sL||(M+=d.relevance),(d=d.parent)!==r.parent;);return r.starts&&(r.endSameAsBegin&&(r.starts.eR=r.eR),u(r.starts)),a.rE?0:n.length}}(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||i(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function M(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function b(e){var n,t,r,a,i,o=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=l.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=o?T(o,i,!0):w(i),(t=E(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=d(t,E(a),i)),r.value=M(r.value),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?c[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,b)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[c[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=M,a.highlightBlock=b,a.configure=function(e){B=s(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}p(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){c[e]=n})},a.listLanguages=function(){return i(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=s,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("cpp",function(e){function t(e){return"(?:"+e+")?"}var r="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",i="("+r+"|"+t(a)+"[a-zA-Z_]\\w*"+t("<.*?>")+")",c={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},s={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[e.BE]},{b:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",e:"'",i:"."},{b:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},n={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},o={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},c:[{b:/\\\n/,relevance:0},e.inherit(s,{cN:"meta-string"}),{cN:"meta-string",b:/<.*?>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},l={cN:"title",b:t(a)+e.IR,relevance:0},u=t(a)+e.IR+"\\s*\\(",p={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},m=[c,e.CLCM,e.CBCM,n,s],d={v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:p,c:m.concat([{b:/\(/,e:/\)/,k:p,c:m.concat(["self"]),relevance:0}]),relevance:0},b={cN:"function",b:"("+i+"[\\*&\\s]+)+"+u,rB:!0,e:/[{;=]/,eE:!0,k:p,i:/[^\w\s\*&:<>]/,c:[{b:r,k:p,relevance:0},{b:u,rB:!0,c:[l],relevance:0},{cN:"params",b:/\(/,e:/\)/,k:p,relevance:0,c:[e.CLCM,e.CBCM,s,n,c,{b:/\(/,e:/\)/,k:p,relevance:0,c:["self",e.CLCM,e.CBCM,s,n,c]}]},c,e.CLCM,e.CBCM,o]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],k:p,i:"",k:p,c:["self",c]},{b:e.IR+"::",k:p},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},e.TM]}]),exports:{preprocessor:o,strings:s,k:p}}});hljs.registerLanguage("ini",function(e){var b={cN:"number",relevance:0,v:[{b:/([\+\-]+)?[\d]+_[\d_]+/},{b:e.NR}]},a=e.C();a.v=[{b:/;/,e:/$/},{b:/#/,e:/$/}];var c={cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"literal",b:/\bon|off|true|false|yes|no\b/},n={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",relevance:10},{b:'"""',e:'"""',relevance:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[a,{cN:"section",b:/\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/,cN:"attr",starts:{e:/$/,c:[a,{b:/\[/,e:/\]/,c:[a,r,c,n,b,"self"],relevance:0},r,c,n,b]}}]}});hljs.registerLanguage("julia",function(e){var r={keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},t="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",a={l:t,k:r,i:/<\//},n={cN:"subst",b:/\$\(/,e:/\)/,k:r},o={cN:"variable",b:"\\$"+t},i={cN:"string",c:[e.BE,n,o],v:[{b:/\w*"""/,e:/"""\w*/,relevance:10},{b:/\w*"/,e:/"\w*/}]},l={cN:"string",c:[e.BE,n,o],b:"`",e:"`"},c={cN:"meta",b:"@"+t};return a.c=[{cN:"number",b:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{cN:"string",b:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,c,{cN:"comment",v:[{b:"#=",e:"=#",relevance:10},{b:"#",e:"$"}]},e.HCM,{cN:"keyword",b:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{b:/<:/}],n.c=a.c,a});hljs.registerLanguage("julia-repl",function(e){return{c:[{cN:"meta",b:/^julia>/,relevance:10,starts:{e:/^(?![ ]{6})/,sL:"julia"}},{cN:"metas",b:/^shell>/,relevance:10,starts:{e:/^(?![ ]{6})/,sL:"bash"}},{cN:"metap",b:/^\(.*\)\spkg>/,relevance:10,starts:{e:/^(?![ ]{6})/,sL:"julia"}}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},a={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,relevance:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],relevance:0},e.HCM,a,{cN:"",b:/\\"/},{cN:"string",b:/'/,e:/'/},t]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},r={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},n=[e.C("#","$",{c:[r]}),e.C("^\\=begin","^\\=end",{c:[r],relevance:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:b},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,rB:!0,c:[{b:/<<[-~]?'?/},{b:/\w+/,endSameAsBegin:!0,c:[e.BE,s]}]}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:b},l=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",relevance:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:c}],relevance:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:b},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),relevance:0}].concat(n);s.c=l;var d=[{b:/^\s*=>/,starts:{e:"$",c:i.c=l}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:b,i:/\/\*/,c:n.concat(d).concat(l)}});hljs.registerLanguage("yaml",function(e){var b="true false yes no null",a={cN:"string",relevance:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,{cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]}]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[{cN:"attr",v:[{b:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{b:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{b:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{cN:"meta",b:"^---s*$",relevance:10},{cN:"string",b:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,relevance:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"\\-(?=[ ]|$)",relevance:0},e.HCM,{bK:b,k:{literal:b}},{cN:"number",b:e.CNR+"\\b"},a]}});hljs.registerLanguage("r",function(e){var r="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{c:[e.HCM,{b:r,l:r,k:{keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{cN:"number",b:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{cN:"number",b:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{cN:"number",b:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{cN:"number",b:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{b:"`",e:"`",relevance:0},{cN:"string",c:[e.BE],v:[{b:'"',e:'"'},{b:"'",e:"'"}]}]}});hljs.registerLanguage("plaintext",function(e){return{disableAutodetect:!0}});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[r],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},r]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",relevance:0},{cN:"bullet",b:"^\\s*([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",relevance:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```\\w*\\s*$",e:"^```[ ]*$"},{b:"`.+?`"},{b:"^( {4}|\\t)",e:"$",relevance:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,relevance:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],relevance:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("cal",function(e){var r="div mod in and or not xor asserterror begin case do downto else end exit for if of repeat then to until while with var",c=[e.CLCM,e.C(/\{/,/\}/,{relevance:0}),e.C(/\(\*/,/\*\)/,{relevance:10})],n={cN:"string",b:/'/,e:/'/,c:[{b:/''/}]},t={cN:"string",b:/(#\d+)+/},a={cN:"function",bK:"procedure",e:/[:;]/,k:"procedure|10",c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:r,c:[n,t]}].concat(c)},o={cN:"class",b:"OBJECT (Table|Form|Report|Dataport|Codeunit|XMLport|MenuSuite|Page|Query) (\\d+) ([^\\r\\n]+)",rB:!0,c:[e.TM,a]};return{cI:!0,k:{keyword:r,literal:"false true"},i:/\/\*/,c:[n,t,{cN:"number",b:"\\b\\d+(\\.\\d+)?(DT|D|T)",relevance:0},{cN:"string",b:'"',e:'"'},e.NM,o,a]}});hljs.registerLanguage("css",function(e){var c={b:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM,e.CSSNM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$",c:[e.ASM,e.QSM]},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(page|font-face)",l:"@[a-z-]+",k:"@page @font-face"},{b:"@",e:"[{;]",i:/:/,rB:!0,c:[{cN:"keyword",b:/@\-?\w[\w]*(\-\w+)*/},{b:/\s/,eW:!0,eE:!0,relevance:0,k:"and or not only",c:[{b:/[a-z-]+:/,cN:"attribute"},e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,c]}]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={b:/\{\{/,relevance:0},l={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,b],relevance:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,b],relevance:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,b,a,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,b,a,c]},{b:/(u|r|ur)'/,e:/'/,relevance:10},{b:/(u|r|ur)"/,e:/"/,relevance:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,a,c]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,a,c]},e.ASM,e.QSM]},n={cN:"number",relevance:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,n,l,e.HCM]};return c.c=[l,n,b],{aliases:["py","gyp","ipython"],k:r,i:/(<\/|->|\?)|=>/,c:[b,n,{bK:"if",relevance:0},l,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}}); 3 | -------------------------------------------------------------------------------- /src/_libs/katex/auto-render.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,function(e){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(t,r){t.exports=e},function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),a=function(e,t,r){for(var n=r,o=0,a=e.length;n.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathdefault{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-weight:700;font-style:italic}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;vertical-align:bottom;position:relative}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;vertical-align:bottom;font-size:1px;width:2px;min-width:2px}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{display:inline-block;width:100%;border-bottom-style:solid}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{width:0;position:relative}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{display:inline-block;border:0 solid;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{display:inline-block;width:100%;border-bottom-style:solid}.katex .hdashline{display:inline-block;width:100%;border-bottom-style:dashed}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .op-limits>.vlist-t{text-align:center}.katex .accent>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{display:block;position:absolute;width:100%;height:inherit;fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex svg path{stroke:none}.katex img{border-style:none;min-width:0;min-height:0;max-width:none;max-height:none}.katex .stretchy{width:100%;display:block;position:relative;overflow:hidden}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{width:100%;position:relative;overflow:hidden}.katex .halfarrow-left{position:absolute;left:0;width:50.2%;overflow:hidden}.katex .halfarrow-right{position:absolute;right:0;width:50.2%;overflow:hidden}.katex .brace-left{position:absolute;left:0;width:25.1%;overflow:hidden}.katex .brace-center{position:absolute;left:25%;width:50%;overflow:hidden}.katex .brace-right{position:absolute;right:0;width:25.1%;overflow:hidden}.katex .x-arrow-pad{padding:0 .5em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{box-sizing:border-box;border:.04em solid}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{text-align:left} 2 | -------------------------------------------------------------------------------- /src/config.md: -------------------------------------------------------------------------------- 1 | 6 | @def website_title = "A guide to data parallelism in Julia" 7 | @def website_descr = "A guide to data parallelism in Julia" 8 | @def website_url = get(ENV, "JULIA_FRANKLIN_WEBSITE_URL", "https://juliafolds.github.io/data-parallelism") 9 | 10 | @def prepath = get(ENV, "JULIA_FRANKLIN_PREPATH", "data-parallelism") 11 | 12 | @def author = "Takafumi Arakaki" 13 | 14 | @def mintoclevel = 2 15 | 16 | 21 | @def ignore = ["node_modules/", "franklin", "franklin.pub"] 22 | 23 | 29 | \newcommand{\R}{\mathbb R} 30 | \newcommand{\scal}[1]{\langle #1 \rangle} 31 | 32 | \newcommand{\kbd}[1]{~~~!#1~~~} 33 | 34 | \newcommand{\note}[1]{@@note @@title 💡 Note@@ @@content #1 @@ @@} 35 | \newcommand{\warn}[1]{@@warn @@title ⚠ Warning@@ @@content #1 @@ @@} 36 | 37 | 44 | \newcommand{\test}[2]{ 45 | @@test 46 | ```julia:/-test-/!#1 47 | #hideall 48 | 49 | !#2 50 | 51 | Base.Text("OK") 52 | ``` 53 | 54 | \testcode{!#2} 55 | \testcheck{!#1} 56 | @@ 57 | } 58 | -------------------------------------------------------------------------------- /src/explanation/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/explanation/libraries.md: -------------------------------------------------------------------------------- 1 | # Libraries for parallelism in Julia 2 | 3 | \warn{This page is a work in progress} 4 | 5 | ```julia:table 6 | #hideall 7 | using UnPack 8 | 9 | repos = split( 10 | """ 11 | https://github.com/tro3/ThreadPools.jl 12 | https://github.com/mohamed82008/KissThreading.jl 13 | https://github.com/baggepinnen/ThreadTools.jl 14 | https://github.com/Jutho/Strided.jl 15 | https://github.com/Jutho/TensorOperations.jl 16 | https://github.com/mcabbott/Tullio.jl 17 | https://github.com/invenia/Parallelism.jl 18 | https://github.com/JuliaParallel/Dagger.jl 19 | https://github.com/omlins/ParallelStencil.jl 20 | https://github.com/tkf/ThreadsX.jl 21 | https://github.com/JuliaFolds/Transducers.jl 22 | https://github.com/JuliaFolds/FLoops.jl 23 | """, 24 | "\n", 25 | keepempty = false, 26 | ) 27 | docs_urls = Dict( 28 | "KissThreading.jl" => nothing, 29 | "Strided.jl" => nothing, 30 | "ThreadTools" => nothing, 31 | "ThreadPools.jl" => "https://tro3.github.io/ThreadPools.jl/", 32 | "Tullio.jl" => nothing, 33 | "ParallelStencil.jl" => "https://github.com/omlins/ParallelStencil.jl#contents", 34 | ) 35 | keywords_mapping = Dict( 36 | "TensorOperations.jl" => "threaded, GPU", 37 | "Tullio.jl" => "threaded, GPU", 38 | "ParallelStencil.jl" => "threaded, distributed, GPU", 39 | "Dagger.jl" => "distributed", 40 | "FLoops.jl" => "threaded, distributed", 41 | "Transducers.jl" => "threaded, distributed", 42 | ) 43 | 44 | projects = map(repos) do repository 45 | m = match(r"github.com/([^/]+)/([^/]+)", repository) 46 | user = m[1] 47 | package = m[2] 48 | docs = get(docs_urls, package, "https://$user.github.io/$package/stable/") 49 | keywords = get(keywords_mapping, package, "threaded") 50 | return ( 51 | package = package, 52 | user = user, 53 | repository = repository, 54 | docs = docs, 55 | keywords = keywords, 56 | ) 57 | end 58 | 59 | sort!(projects; by = x -> x.package) 60 | 61 | println("| Package | User/Org. | Repository | Documentation | |") 62 | println("| --- | --- | --- | --- | --- |") 63 | for project in projects 64 | @unpack package, user, repository, docs, keywords = project 65 | code_badge = "[![GitHub](https://img.shields.io/github/stars/$user/$package?style=social)][$package-code]" 66 | if docs === nothing 67 | docs_badge = "" 68 | else 69 | docs_badge = "[![Documentation](https://img.shields.io/badge/docs-$package-blue.svg)][$package-docs]" 70 | end 71 | href = lowercase(replace(package, "." => "-")) 72 | println( 73 | "| **[", package, "](#", href, ")**", 74 | " | ", "**`@", user, "`**", 75 | " | ", code_badge, 76 | " | ", docs_badge, 77 | " | ", keywords, 78 | " |", 79 | ) 80 | end 81 | println() 82 | 83 | for project in projects 84 | @unpack package, repository, docs = project 85 | println("[$package-code]: $repository") 86 | println("[$package-docs]: $docs") 87 | end 88 | ``` 89 | 90 | \textoutput{table} 91 | 92 | ## Threaded 93 | 94 | \label{threadpools-jl} 95 | ### ThreadPools.jl 96 | 97 | A custom thread pool framework for separating out latency-critical 98 | code (e.g., GUI) from throughput-oriented code. 99 | 100 | \label{kissthreading-jl} 101 | ### KissThreading.jl 102 | 103 | \label{threadtools-jl} 104 | ### ThreadTools.jl 105 | 106 | \label{strided-jl} 107 | ### Strided.jl 108 | 109 | Threaded broadcasting, mapping, and reduce. 110 | 111 | \label{tensoroperations-jl} 112 | ### TensorOperations.jl 113 | 114 | Indexing notation-based domain specific language and the execution 115 | backends for multi-threading and GPU. 116 | 117 | \label{tullio-jl} 118 | ### Tullio.jl 119 | 120 | Indexing notation-based domain specific language and the execution 121 | backends for multi-threading and GPU. Automatically provide 122 | derivatives. 123 | 124 | \label{parallelism-jl} 125 | ### Parallelism.jl 126 | 127 | \label{floops-jl} 128 | ### FLoops.jl 129 | 130 | \label{threadsx-jl} 131 | ### ThreadsX.jl 132 | 133 | \label{transducers-jl} 134 | ### Transducers.jl 135 | 136 | ## Distributed 137 | 138 | \label{dagger-jl} 139 | ### Dagger.jl 140 | -------------------------------------------------------------------------------- /src/explanation/semidirect-products.md: -------------------------------------------------------------------------------- 1 | # Parallel reductions using semidirect products 2 | 3 | Solutions to many parallel programming problems can be reduced to a 4 | well-designed monoid[^semigroup]. As such, interesting parallel solution 5 | sometimes require non-trivial monoids. To enrich the repertoire, let us discuss 6 | _semidirect product_; a particularity elegant construction of useful monoids. 7 | 8 | \tableofcontents 9 | 10 | [^semigroup]: Here, we mainly discuss monoid and monoid actions. However, since one can always turn a semigroup into a monoid by adjoining the identity, the following discussion also applies to semigroups which is more useful than monoid in dynamic languages like Julia. 11 | 12 | \note{ 13 | The code examples here focus on simplicity rather than the performance and 14 | actually executing them in parallel. However, it is straightforward to use 15 | exactly the same pattern and derive efficient implementations in Julia. See 16 | [A quick introduction to data parallelism in Julia](../../tutorials/quick-introduction/) and 17 | [Efficient and safe approaches to mutation in data parallelism](../../tutorials/mutations/) 18 | for the main ingredients required. 19 | } 20 | 21 | \label{maxdepth} 22 | ## Maximum depth of parentheses 23 | 24 | As a motivating example, let us compute the maximum depth of parentheses in a 25 | string. Each position of a string has an integer _depth_ which is one more 26 | larger (smaller) than the depth of the previous position if the current 27 | character is the open parenthesis `(` (close parenthesis `)`). Otherwise the 28 | depth is equal to the depth of the previous position. The initial depth is 0. 29 | We are interested in the maximum depth of a given string: 30 | 31 | ```plaintext 32 | string ( ( ( ) ) ( ) ) 33 | depth 1 2 3 2 1 2 1 0 34 | ↑ 35 | maximum depth 36 | ``` 37 | 38 | The following code is a Julia program that calculates the maximum depth. It 39 | computes the depth of every position using the cumulative sum function `cumsum` 40 | and then take the `maximum` of it: 41 | 42 | ```julia:parentheses1 43 | parentheses = collect("(((())((()))) ((())))") 44 | increments = ifelse.(parentheses .== '(', +1, ifelse.(parentheses .== ')', -1, 0)) 45 | depths = cumsum(increments) 46 | maximum(depths) 47 | ``` 48 | 49 | \show{parentheses1} 50 | 51 | ```julia:parentheses2 52 | # hideall 53 | using Plots 54 | 55 | function plot_maxdepth(parentheses; xs = eachindex(parentheses), suffix = "") 56 | parentheses = collect(parentheses) 57 | increments = ifelse.(parentheses .== '(', +1, ifelse.(parentheses .== ')', -1, 0)) 58 | depths = cumsum(increments) 59 | dmax, imax = findmax(depths) 60 | imax += first(xs) - 1 61 | plt_depth = plot( 62 | [first(xs)-1; xs], 63 | [0; depths], 64 | seriestype = :steppost, 65 | label = "", 66 | ylabel = "depths" * suffix, 67 | ylim = (min(0, minimum(depths)), maximum(depths) + 2), 68 | ) 69 | plot!( 70 | plt_depth, 71 | [imax], 72 | [dmax], 73 | label = "", 74 | markershape = :o, 75 | # xticks = nothing, 76 | annotations = ( 77 | imax - 2, 78 | dmax + 0.5, 79 | text("maximum(depths$suffix)", :left, :bottom), 80 | ), 81 | ) 82 | plt_inc = bar( 83 | xs, 84 | increments, 85 | label = "", 86 | ylabel = "increments" * suffix, 87 | xticks = (xs, parentheses), 88 | yticks = [-1, 0, 1], 89 | ) 90 | plt = plot( 91 | plt_depth, 92 | plt_inc, 93 | layout = (:, 1), 94 | link = :x, 95 | ) 96 | return plt 97 | end 98 | savefig(plot_maxdepth(parentheses), joinpath(@OUTPUT, "parentheses2.png")) 99 | ``` 100 | 101 | ```julia:parentheses3 102 | # hideall 103 | using Plots 104 | 105 | function plot_depths(parentheses; xs = eachindex(parentheses), suffix = "", kwargs...) 106 | parentheses = collect(parentheses) 107 | increments = ifelse.(parentheses .== '(', +1, ifelse.(parentheses .== ')', -1, 0)) 108 | depths = cumsum(increments) 109 | dmax, imax = findmax(depths) 110 | imax += first(xs) - 1 111 | plt_depth = plot(; xlim = (first(xs)-1, last(xs)+1), kwargs...) 112 | hline!( 113 | plt_depth, 114 | [0]; 115 | color = :black, 116 | linestyle = :dot, 117 | label = "", 118 | 119 | ) 120 | plot!( 121 | plt_depth, 122 | [first(xs)-1; xs], 123 | [0; depths]; 124 | color = 1, 125 | seriestype = :steppost, 126 | label = "", 127 | ) 128 | plot!( 129 | plt_depth, 130 | [imax], 131 | [dmax], 132 | color = 2, 133 | label = "", 134 | markershape = :o, 135 | # xticks = nothing, 136 | annotations = ( 137 | imax, 138 | dmax + 0.5, 139 | text("m$suffix", :left, :bottom), 140 | ), 141 | ) 142 | plot!( 143 | plt_depth, 144 | [xs[end]], 145 | [depths[end]], 146 | color = 3, 147 | label = "", 148 | markershape = :rect, 149 | # xticks = nothing, 150 | annotations = ( 151 | xs[end] + 0.4, 152 | depths[end], 153 | text("d$suffix", :left), 154 | ), 155 | ) 156 | # plot!( 157 | # plt_depth, 158 | # [xs[end], xs[end]] .+ 0.5, 159 | # [0, depths[end]], 160 | # arrow = true, 161 | # label = "", 162 | # ) 163 | return plt_depth 164 | end 165 | 166 | function depth_and_maximum(parentheses) 167 | parentheses = collect(parentheses) 168 | increments = ifelse.(parentheses .== '(', +1, ifelse.(parentheses .== ')', -1, 0)) 169 | depths = cumsum(increments) 170 | return (depths[end], maximum(depths)) 171 | end 172 | 173 | let 174 | m = length(parentheses) ÷ 3 175 | ymin, ymax = extrema([0; depths]) 176 | ylim = (ymin - 1, ymax + 2) 177 | chunk1 = parentheses[1:m] 178 | chunk2 = parentheses[m+1:2m] 179 | chunk3 = parentheses[2m+1:end] 180 | 181 | global d1, m1 = depth_and_maximum(chunk1) 182 | global d2, m2 = depth_and_maximum(chunk2) 183 | global d3, m3 = depth_and_maximum(chunk3) 184 | 185 | plt = plot( 186 | plot_depths(chunk1; suffix = 1, title = "chunk 1", ylim), 187 | plot_depths(chunk2; xs = m+1:2m, suffix = 2, ylim = ylim .- depths[m], title = "chunk 2"), 188 | plot_depths(chunk3; xs = 2m+1:length(parentheses), suffix = 3, ylim = ylim .- depths[2m], title = "chunk 3"), 189 | layout = (1, :), 190 | size = (800, 200), 191 | ) 192 | savefig(plt, joinpath(@OUTPUT, "parentheses3.png")) 193 | end 194 | ``` 195 | 196 | \fig{parentheses2.png} 197 | 198 | Both `cumsum` (prefix sum) and `maximum` (reduce) can be computed in parallel. 199 | However, it is possible to fuse these operations and compute the maximum depth 200 | in one sweep (which is essential for performance). To solve this problem with 201 | single-path reduction, we need to find a small set of states "just enough" for 202 | combining sub-solutions obtained by solving each chunk of the input. An 203 | important observation here is that the "humps" in the `depths` plot can be 204 | easily shifted up and down when combining into the result from preceding (left) 205 | string of parentheses. This motivates us to track the maximum and the final 206 | (relative) depth of a chunk of the input string. Splitting the above solution 207 | into three chunks, we get the following three sub-solutions: 208 | 209 | \fig{parentheses3.png} 210 | 211 | Consider chunk 1 and 2. Combining the final depths (`d1` and `d2`) is as easy 212 | as summing these two depths. To combine the maximum in each chunk, we need to 213 | shift the right maximum `m2` by the final depth of the left chunk `d1` so that 214 | both maximum "candidates" `m1` and `m2` are compared with respect to the same 215 | reference point (i.e., the beginning of the left chunk). Thus, we get the 216 | following function for combining the solutions `(d1, m1)` and `(d2, m2)` from 217 | two consecutive chunks: 218 | 219 | ```julia:parentheses4 220 | function ⊗((d1, m1), (d2, m2)) 221 | d = d1 + d2 # total change in depth 222 | 223 | m2′ = d1 + m2 # shifting the maximum of the right chunk before comparison 224 | m = max(m1, m2′) # find the maximum 225 | 226 | return (d, m) 227 | end 228 | ``` 229 | 230 | Given the sub-solutions above 231 | 232 | ```julia:parentheses5 233 | #hideall 234 | @show d1, m1 235 | @show d2, m2 236 | @show d3, m3 237 | ``` 238 | 239 | \output{parentheses5} 240 | 241 | we can compute the final result 242 | 243 | ```julia:parentheses6 244 | ans = begin # hide 245 | (d1, m1) ⊗ (d2, m2) ⊗ (d3, m3) 246 | end # hide 247 | @assert ans[2] == maximum(depths) # hide 248 | ans # hide 249 | ``` 250 | 251 | \show{parentheses6} 252 | 253 | where the second element of the tuple is the maximum depth. As we will see in 254 | the next section, `⊗` is associative. Thus, we can use it as the input to 255 | `reduce`. Observing singleton chunk `[x]` (where `x` is -1, 0, or 1) has the 256 | trivial solution `(x, x)` (i.e., the last element of `[x]` is `x` and the 257 | maximum element of `[x]` is `x`), we get the single-sweep reduction to calculate 258 | the maximum depth of the parentheses: 259 | 260 | ```julia:parentheses7 261 | ans = begin # hide 262 | mapreduce(x -> (x, x), ⊗, increments) 263 | end # hide 264 | @assert ans[2] == maximum(depths) # hide 265 | ans # hide 266 | ``` 267 | 268 | \show{parentheses7} 269 | 270 | Recall that `mapreduce` can also be computed using the left-fold `mapfoldl`. By 271 | manually inlining the definitions of `mapfoldl(x -> (x, x), ⊗, increments)`, we 272 | also obtain the following sequential algorithm: 273 | 274 | ```julia 275 | function maxdepth_seq1(increments) 276 | d1 = m1 = 0 277 | for x in increments 278 | d2 = m2 = x 279 | 280 | # Inlining `⊗`: 281 | d = d1 + d2 282 | m2′ = d1 + m2 283 | m = max(m1, m2′) 284 | 285 | d1 = d 286 | m1 = m 287 | end 288 | return m1 289 | end 290 | ``` 291 | 292 | Since `d1 + d2` and `d1 + m2` are equivalent when `d2 = m2 = x`, we can do 293 | "manual Common Subexpression Elimination" to get: 294 | 295 | ```julia 296 | function maxdepth_seq2(increments) 297 | d1 = m1 = 0 298 | for x in increments 299 | d1 = d1 + x 300 | m1 = max(m1, d1) 301 | end 302 | return m1 303 | end 304 | ``` 305 | 306 | Note that this is the straightforward sequential solution to the original 307 | problem. Compilers such as Julia+LLVM may be able to do this "derivation" 308 | as a part of optimization in may programs. It is also possible to implement this 309 | with sufficiently rich "monoid combinator" frameworks such as 310 | [Transducers.jl](https://github.com/JuliaFolds/Transducers.jl) (using `next` and 311 | `combine` specializations). 312 | 313 | ## Semidirect products (restricted version) 314 | 315 | The structure in `⊗` of the previous section is actually very generic and 316 | applicable to many other reductions. If we replace `+` with `*′` and `max` with 317 | `+′`, we obtain the following higher-order function (combinator) `sdpl`. Let us 318 | also define a similar "flipped" version `sdpr`. 319 | 320 | ```julia:sdp1 321 | sdpl(*′, +′) = ((a₁, b₁), (a₂, b₂)) -> (a₁ *′ a₂, b₁ +′ (a₁ *′ b₂)) 322 | sdpr(*′, +′) = ((a₁, b₁), (a₂, b₂)) -> (a₁ *′ a₂, (b₁ *′ a₂) +′ b₂) 323 | ``` 324 | 325 | A binary function `(x, y) -> sdpl(*′, +′)(x, y)` is associative (i.e., 326 | semigroup) if `*′` and `+′` are both associative and `*′` is left-distributive 327 | over `+′`; i.e., 328 | 329 | \label{eq-distl-op} 330 | $$ 331 | \tag{distl-op} 332 | x *' (y +' z) = (x *' y) +' (x *' z) 333 | $$ 334 | 335 | Similarly, `sdpr(*′, +′)` is associative if `*′` and `+′` are both associative 336 | and `*′` is right-distributives over `+′`. See, e.g., [Kogge and Stone (1973)], 337 | [Blelloch (1990)], [Gorlatch and Lengauer (1997)], [Chauhan et al. (2016)], and 338 | [Kmett (2018)] for the discussion and applications on this algebraic fact (or 339 | its generalized form; see below). The monoid combinator `sdpl` of this form in 340 | particular is described in [Gorlatch and Lengauer (1997)], including the proofs 341 | we skipped here. They described it as _scan-reduce composition_ and _scan-scan 342 | composition_ theorems. As we saw in the previous section, `mapreduce(x -> (x, 343 | x), sdpl(+, max), increments)` computes `maximum(cumsum(increments))` 344 | efficiently by composing (fusing) the scan (`cumsum`) and reduce (`maximum`). 345 | 346 | Borrowing the terminology from group theory (see [Semidirect product - 347 | Wikipedia](https://en.wikipedia.org/wiki/Semidirect_product)), and also 348 | following [Chauhan et al. (2016)]'s nomenclature, let us call `sdpl` and `sdpr` 349 | the _semidirect products_ although the definition above is not of its fully 350 | generalized form. 351 | 352 | Other than the usual multiplication-addition pair (`*`, `+`) and addition-max 353 | pair (`+`, `max`) as discussed in the previous section, there are various pairs 354 | of monoids satisfying left- and/or right-distributivity property (see, 355 | [Distributive property - 356 | Wikipedia](https://en.wikipedia.org/wiki/Distributive_property)). For example, 357 | following functions can be used with `sdpl` and `sdpr` (on appropriate domain 358 | types): 359 | 360 | | `*′` | `+′` | Example applications | 361 | | --- | --- | --- | 362 | | `*` | `+` | linear recurrence (see below) | 363 | | `+` | `max` | maximum depth of parentheses (see above) | 364 | | `+` | `min` | | 365 | | `min` | `max` | | 366 | | `max` | `min` | | 367 | | `∩` | `∪` | `unique` (see below); GEN/KILL-sets | 368 | | `∪` | `∩` | | 369 | 370 | The `unique` example (below) and GEN-/KILL-sets example in [Chauhan et al. 371 | (2016)] can be considered an instance of `sdpl(∩, ∪)` with the first monoid `∩` 372 | virtually operating on the complement sets. 373 | 374 | ## Linear recurrence 375 | 376 | Semidirect product `sdpr(*′, +′)` is a generalized from of linear recurrence 377 | equation: 378 | 379 | \label{eq-linrec} 380 | $$ 381 | \tag{linrec} 382 | x_t = x_{t-1} a_t + b_t \qquad (t=1,2,...,T) 383 | $$ 384 | 385 | given sequences $(a_t)_{t=1}^{T}$ and $(b_t)_{t=1}^T$. Let us use the initial 386 | condition $x_0 = 0$ for simplicity (as specifying $x_0$ is equivalent to 387 | specifying $b_1$). Indeed, `(1, 0)` is the identity element for the method 388 | `sdpr(*, +)(_::Number, _::Number)`. To see `sdpr(*, +)` computes 389 | $(x_t)_{t=1}^T$, let us manually expand `foldl(sdpr(*, +), zip(as, bs); init = 390 | (1, 0))`, as we did for `maxdepth_seq1` (or, alternatively, see [Blelloch 391 | (1990)]): 392 | 393 | ```julia 394 | function linear_recurrence_seq1(as, bs) 395 | a₁ = 1 396 | b₁ = 0 397 | for (a₂, b₂) in zip(as, bs) 398 | # Inlining `sdpr(*, +)`: 399 | a₁ = a₁ * a₂ 400 | b₁ = b₁ * a₂ + b₂ 401 | end 402 | return (a₁, b₁) 403 | end 404 | ``` 405 | 406 | By eliminating `a₁` and renaming `b₁` to `x`, we have 407 | 408 | ```julia 409 | function linear_recurrence_seq2(as, bs) 410 | x = 0 411 | for (a, b) in zip(as, bs) 412 | x = x * a + b 413 | end 414 | return x 415 | end 416 | ``` 417 | 418 | which computes the linear recurrence equation [(linrec)](#eq-linrec). 419 | (Note: the first component `a₁ * a₂` is still required for associativity of 420 | `sdpr(*, +)`. It keeps track of the multiplier $\prod_{t=p}^q a_t$ for 421 | propagating the effect of $x_{p-1}$ to $x_q$.) 422 | 423 | The above code also work when the elements in `as` and `bs` are matrices. In 424 | particular, `x` and `b` can be "row vectors." Even though Julia uses 1×n 425 | matrices for row vectors, this observation indicates that `a`s and `b`s can live 426 | in different spaces. Indeed, we will see that it is useful to "disassociate" the 427 | functions for `a₁ *′ a₂` and `b₁ *′ a₂`. 428 | 429 | ## Semidirect products and distributive monoid actions 430 | 431 | The combinators `sdpl` and `sdpr` can be generalized to the case where `a`s and 432 | `b`s are of different types. We can simply extend these combinators to take 433 | three functions[^multipledispatch] : 434 | 435 | ```julia:sdp2 436 | sdpl(*′, ⊳, +′) = ((a₁, b₁), (a₂, b₂)) -> (a₁ *′ a₂, b₁ +′ (a₁ ⊳ b₂)) 437 | sdpr(*′, ⊲, +′) = ((a₁, b₁), (a₂, b₂)) -> (a₁ *′ a₂, (b₁ ⊲ a₂) +′ b₂) 438 | ``` 439 | 440 | [^multipledispatch]: In Julia, we acn use multiple dispatch for plumbing the calls `a₁ *′ a₂` and `b₁ *′ a₂` to different implementations. However, it would require defining particular type for each pair of `a₁ *′ a₂` and `b₁ *′ a₂`. Thus, it is more convenient to define these ternary combinators. 441 | 442 | The binary function `(x, y) -> sdpl(*′, ⊳, +′)(x, y)` is a monoid if `*′` and 443 | `+′` are monoids, the function `⊳` is a left _monoid action_[^semigroupaction]; 444 | i.e., it satisfies 445 | 446 | \label{eq-actl} 447 | $$ 448 | \tag{actl} 449 | a_1 \rhd (a_2 \rhd b) = (a_1 *' a_2) \rhd b 450 | $$ 451 | 452 | and it is left-_distributive_ over `+'`; i.e., 453 | 454 | \label{eq-distl-act} 455 | $$ 456 | \tag{distl-act} 457 | a \rhd (b_1 +' b_2) = (a \rhd b_1) +' (a \rhd b_2). 458 | $$ 459 | 460 | The first condition indicates we can either 461 | 462 | 1. apply actions sequentially (e.g., $A_1 (A_2 x)$ where $A_1$ and $A_2$ are 463 | matrices and $x$ is a vector) or 464 | 2. combine actions first and _then_ apply the combined function (e.g., $(A_1 465 | A_2) x$). 466 | 467 | The second condition indicates that we can either 468 | 469 | 1. merge the "targets" $b_1$ and $b_2$ first (e.g., $A (x_1 + x_2)$ where $A$ is 470 | a matrix and $x_1$ and $x_2$ are vectors) or 471 | 2. apply the actions separately and _then_ merge them (e.g., $A x_1 + A x_2$). 472 | 473 | These extra freedom in how to compute the result is essential in the parallel 474 | computing and is captured by the property that `(x, y) -> sdpl(*′, ⊳, +′)(x, y)` 475 | is associative. 476 | 477 | If $*' = \rhd$, the condition for monoid action [(actl)](#eq-actl) is simply the 478 | associativity condition and the condition for left-distributivity of the monoid 479 | action [(distl-act)](#eq-distl-act) is equivalent to the left-distribuity of the 480 | modnoid [(distl-op)](#eq-distl-op). 481 | 482 | For `sdpr(*′, ⊲, +′)`, the function `⊲` must be a right monoid action 483 | 484 | \label{eq-actr} 485 | $$ 486 | \tag{actr} 487 | (b \lhd a_1) \lhd a_2 = b \lhd (a_1 *' a_2) 488 | $$ 489 | 490 | that right-distributes over `+'` 491 | 492 | \label{eq-distr-act} 493 | $$ 494 | \tag{distr-act} 495 | (b_1 +' b_2) \lhd a = (b_1 \lhd a) +' (b_2 \lhd a). 496 | $$ 497 | 498 | This construction of monoids `sdpl(*′, ⊳, +′)` and `sdpr(*′, ⊲, +′)` are called 499 | [_semidirect 500 | product_](https://en.wikipedia.org/wiki/Semidirect_product)[^semiassociative]. 501 | 502 | [^semigroupaction]: It may be easier to find the resources on [semigroup action](https://en.wikipedia.org/wiki/Semigroup_action). A monoid action is simply a semigroup action that is also a monoid. 503 | 504 | [^semiassociative]: [Kogge and Stone (1973)] and [Blelloch (1990)] use the term _semiassociative_ for describing the property required for the function `⊲`. 505 | 506 | As an aside, observe that more "obvious" way to combine two monoids (i.e., 507 | direct product) 508 | 509 | ```julia:prodop 510 | prodop(⊗₁, ⊗₂) = ((xa, xb), (ya, yb)) -> (xa ⊗₁ ya, xb ⊗₂ yb) 511 | 512 | ans = begin # hide 513 | mapreduce(x -> (x, x), prodop(+, *), 1:10; init = (0, 1)) # example 514 | end # hide 515 | ans == (sum(1:10), prod(1:10)) # hide 516 | ans # hide 517 | ``` 518 | 519 | \show{prodop} 520 | 521 | is a special case of semidirect product with trivial "do-nothing" actions `a ⊳ b 522 | = b` or `b ⊲ a = b`. 523 | 524 | ## Order-preserving `unique` 525 | 526 | Julia's 527 | [`unique`](https://docs.julialang.org/en/v1/base/collections/#Base.unique) 528 | function returns a vector of unique element in the input collection while 529 | preserving the order that the elements appear in the input. To parallelize this 530 | function, we track the unique elements in a set and also keep the elements in a 531 | vector to maintain the ordering. When combining two solutions, we need to filter 532 | out elements in the right chunk if they already appear in the left chunk. This 533 | can be implemented by using `setdiff` for the left action on the vector. 534 | 535 | ```julia:unique1 536 | function vector_unique(xs) 537 | singletons = ((Set([x]), [x]) for x in xs) 538 | monoid = sdpl(union, (a, b) -> setdiff(b, a), vcat) 539 | return last(reduce(monoid, singletons)) 540 | end 541 | ``` 542 | 543 | Example: 544 | 545 | ```julia:unique2 546 | vector_unique([1, 2, 1, 3, 1, 2, 1]) 547 | ``` 548 | 549 | \show{unique2} 550 | 551 | ## Most nested position of parentheses 552 | 553 | Using the general form of semidirect product, we can compute the maximum depth 554 | of parentheses and the corresponding index ("key"). It can be done with a 555 | function `maxpair` that keeps track of the maximum value and the index in the 556 | second monoid. The action `shiftvalue` can be used to shift the value but not 557 | the index: 558 | 559 | ```julia:findmax_parentheses 560 | maxpair((i, x), (j, y)) = y > x ? (j, y) : (i, x) 561 | shiftvalue(d, (i, x)) = (i, d + x) 562 | 563 | function findmax_parentheses(increments) 564 | singletons = ((x, (i, x)) for (i, x) in pairs(increments)) 565 | monoid = sdpl(+, shiftvalue, maxpair) 566 | return reduce(monoid, singletons)[2] 567 | end 568 | 569 | ans = begin # hide 570 | findmax_parentheses(increments) 571 | end # hide 572 | @assert ans == (9, 5) # hide 573 | ans # hide 574 | ``` 575 | 576 | \show{findmax_parentheses} 577 | 578 | (Compare this result with [the first section](#maxdepth).) 579 | 580 | In the above example, we assumed that the key (position/index) is cheap to 581 | compute. However, we may need to use reduction to compute the key as well. For 582 | example, the input may be a UTF-8 string and we need the number of characters 583 | (code points) as the key. Another situation that this is requried is when 584 | processsing newline delimited strings and we need the line column (and/or the 585 | line number) as the key. We can compute the index on-the-fly by **applying 586 | `sdpl` twice**. The "inner" application is the monoid used in the 587 | `findmax_parentheses` function above. The "outer" application of `sdpl` is used 588 | to for 1️⃣ counting the processed number of elemetns and 2️⃣ shifting the index 589 | using the left action: 590 | 591 | ```julia:findmax_parentheses_relative 592 | shiftindex(n, (d, (i, x))) = (d, (n + i, x)) 593 | # ~~~~~ 594 | # shift the index i by the number n of processed elements 595 | 596 | function findmax_parentheses_relative(increments) 597 | singletons = ((1, (x, (1, x))) for x in increments) 598 | # | | 599 | # | the index of the maximum element in a singleton collection 600 | # the number of elements in a singleton collection 601 | 602 | monoid = sdpl(+, shiftindex, sdpl(+, shiftvalue, maxpair)) 603 | # ~ ~.~~~~~~~~ ~~~.~~~~~~~~~~~~~~~~~~~~~~~~ 604 | # | | `-- compute maximum of depths and its index (as above) 605 | # | | 606 | # | `-- 2️⃣ shift the index of right chunk by the size of processed left chunk 607 | # | 608 | # `-- 1️⃣ compute the size of processed chunk 609 | 610 | return reduce(monoid, singletons)[2][2] 611 | end 612 | 613 | ans = begin # hide 614 | findmax_parentheses_relative(increments) 615 | end # hide 616 | @assert ans == (9, 5) # hide 617 | ans # hide 618 | ``` 619 | 620 | \show{findmax_parentheses_relative} 621 | 622 | Since `shiftindex` acts only on the index portion of the state of `sdpl(+, 623 | shiftvalue, maxpair)` and the index does not interact with the other components, 624 | it is easy to see that this satisfy the condition for semidirect product. 625 | 626 | 627 | \test{findmax_parentheses_relative}{ 628 | 629 | # ...which does not mean we shouldn't be unit-testing it 630 | using Test 631 | @testset begin 632 | ⊗ = sdpl(+, shiftindex, sdpl(+, shiftvalue, maxpair)) 633 | prod3(xs) = Iterators.product(xs, xs, xs) 634 | nfailed = 0 635 | for (n1, n2, n3) in prod3(1:3), 636 | (d1, d2, d3) in prod3(1:3), 637 | (i1, i2, i3) in prod3('a':'c'), 638 | (m1, m2, m3) in prod3(1:3) 639 | x1 = (n1, (d1, (i1, m1))) 640 | x2 = (n2, (d2, (i2, m2))) 641 | x3 = (n3, (d3, (i3, m3))) 642 | nfailed += !(x1 ⊗ (x2 ⊗ x3) == (x1 ⊗ x2) ⊗ x3) 643 | end 644 | @test nfailed == 0 645 | end 646 | 647 | } 648 | 649 | 661 | 662 | ## Conclusion 663 | 664 | Semidirect products appear naturally in reductions where multiple states 665 | interact. It helps us easily derive efficient and intricate parallel programs. 666 | Furthermore, even if semidirect product is not required to derive the monoid, it 667 | is cumbersom to convince oneself that the given definition and implementation of 668 | the monoid indeed satisfy the algebraic requirements. Decomposing the monoid 669 | into simpler "sub-component" monoids and monoid actions makes it easy to reason 670 | about such algebraic properties. 671 | 672 | ## References 673 | 674 | [Kogge and Stone (1973)]: #kogge1973 675 | [Blelloch (1990)]: #blelloch1990 676 | [Gorlatch and Lengauer (1997)]: #gorlatch1997 677 | [Chauhan et al. (2016)]: #chauhan2016 678 | [Kmett (2018)]: #kmett2018 679 | 680 | * \label{kogge1973} Kogge, Peter M., and Harold S. Stone. 1973. “A Parallel Algorithm for the Efficient Solution of a General Class of Recurrence Equations.” IEEE Transactions on Computers C–22 (8): 786–93. . 681 | 682 | * \label{Blelloch1990} Blelloch, Guy E. “Prefix Sums and Their Applications,” 1990, 26. 683 | 684 | * \label{Gorlatch1997} Gorlatch, S., and C. Lengauer. “(De) Composition Rules for Parallel Scan and Reduction.” In Proceedings. Third Working Conference on Massively Parallel Programming Models (Cat. No.97TB100228), 23–32, 1997. . 685 | 686 | * \label{chauhan2016} Chauhan, Satvik, Piyush P. Kurur, and Brent A. Yorgey. 2016. “How to Twist Pointers without Breaking Them.” In Proceedings of the 9th International Symposium on Haskell, 51–61. Haskell 2016. New York, NY, USA: Association for Computing Machinery. . 687 | 688 | * \label{kmett2018} Kmett, Edward. 2018. “There and Back Again.” Presented at the Lambda World, Seattle, USA. . 689 | -------------------------------------------------------------------------------- /src/howto/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently asked questions 2 | 3 | \label{set-nthreads-at-run-time} 4 | ## Can I change the number of execution threads without restarting `julia`? 5 | 6 | The number of execution threads is specified at the time `julia` is 7 | started (by command line option `-t`/`--threads` or 8 | `JULIA_NUM_THREADS` environment variable). Thus, to benchmark a 9 | generic Julia program with a different number of threads, you would 10 | need to start a new `julia` process each time. 11 | 12 | Some libraries such as Transducers.jl, FLoops.jl, ThreadsX.jl, and 13 | Parallelism.jl support `basesize` option (see, e.g., 14 | [`Transducers.foldxt`](https://juliafolds.github.io/Transducers.jl/dev/reference/manual/#Transducers.foldxt)). 15 | It is used for specifying the size of the chunk of input collection 16 | processed by one task. Thus, to simulate running code with `N` 17 | threads, you can pass 18 | 19 | ```julia 20 | basesize = length(input_collection) ÷ N 21 | ``` 22 | 23 | to run a multi-threaded function (provided that `N ≤ 24 | Threads.nthreads()` and there is no other function spawning tasks). 25 | For example: 26 | 27 | ```julia-repl 28 | julia> using ThreadsX, BenchmarkTools 29 | 30 | julia> sum_nthreads(f, xs, N) = ThreadsX.sum(f, xs; basesize = length(xs) ÷ N); 31 | 32 | julia> @btime sum_nthreads(sin, 1:1_000_000, 1); 33 | 16.570 ms (5 allocations: 336 bytes) 34 | 35 | julia> @btime sum_nthreads(sin, 1:1_000_000, 2); 36 | 8.318 ms (46 allocations: 2.56 KiB) 37 | 38 | julia> @btime sum_nthreads(sin, 1:1_000_000, 4); 39 | 4.403 ms (128 allocations: 7.03 KiB) 40 | ``` 41 | 42 | Note that this trick cannot be used for experimenting the effect of 43 | the number of threads to the load-balancing of multi-threaded code 44 | since load-balancing requires starting more than `Threads.nthreads()` 45 | tasks. 46 | 47 | \label{multi-threading-vs-multi-processing} 48 | ## Should I use multi-threading? Or should I use multi-processing? 49 | 50 | Julia supports 51 | [threading-based](https://docs.julialang.org/en/v1/manual/multi-threading/) 52 | (via `Base.Threads`) and 53 | [process-based](https://docs.julialang.org/en/v1/manual/distributed-computing/) 54 | (via [Distributed.jl]) parallelism paradigms. Each paradigm has pros and 55 | cons. Choosing the best option requires understanding what your 56 | program does. 57 | 58 | Multi-threading is better for processing complex and large objects 59 | whose serialization become bottleneck in multi-processing -based 60 | parallelism. Note that 61 | [DistributedArrays.jl](https://github.com/JuliaParallel/DistributedArrays.jl) 62 | can be used to reduce serialization overhead in multi-processing. 63 | 64 | If your code allocates many intermediate objects, multi-processing -based 65 | frameworks such as [Distributed.jl] standard library, [Dagger.jl], or MPI.jl 66 | are better option. This is because `julia`'s memory management system 67 | (garbage collection; GC) can be a bottleneck for scaling such type of code to 68 | many execution threads. 69 | 70 | To make it easy to balance with these trade-offs, it is recommended to use a 71 | high-level of abstraction such as data parallelism that helps you switch 72 | underlying execution mechanisms. For example JuliaFolds packages such as 73 | [Folds.jl], [FLoops.jl], and [Transducers.jl] have _executor_ argument to 74 | easily switch thread-based and process-based execution mechanisms. 75 | 76 | [Distributed.jl]: https://docs.julialang.org/en/v1/stdlib/Distributed/ 77 | [Dagger.jl]: https://github.com/JuliaParallel/Dagger.jl 78 | [Folds.jl]: https://github.com/JuliaFolds/Folds.jl 79 | [FLoops.jl]: https://github.com/JuliaFolds/FLoops.jl 80 | [Transducers.jl]: https://github.com/JuliaFolds/Transducers.jl 81 | 82 | ## Why is the approach using `state[threadid()]` not mentioned? 83 | 84 | See: [What is the difference of `@reduce` and `@init` to the approach using 85 | `state[threadid()]`? · FAQ · 86 | FLoops](https://juliafolds.github.io/FLoops.jl/dev/explanation/faq/#faq-state-threadid) 87 | -------------------------------------------------------------------------------- /src/howto/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | # Data-parallel programming in Julia 2 | 3 | \note{ 4 | If you are new to parallel programming, start from 5 | [A quick introduction to data parallelism in Julia](tutorials/quick-introduction). 6 | } 7 | 8 | ## Table of contents 9 | 10 | ```julia:globaltoc 11 | #hideall 12 | 13 | let root = @__DIR__ 14 | chapters = [ 15 | "tutorials" => "Tutorials", 16 | "howto" => "How-to guides", 17 | "reference" => "References", 18 | "explanation" => "Explanation", 19 | ] 20 | firsts = Dict( 21 | "tutorials" => [ 22 | "quick-introduction.md", 23 | "mutations.md", 24 | "concurrency-patterns.md", 25 | ], 26 | ) 27 | external = Dict( 28 | "tutorials" => [ 29 | "[Transducers.jl / Parallel processing tutorial](https://juliafolds.github.io/Transducers.jl/stable/tutorials/tutorial_parallel/)", 30 | "[Transducers.jl / Splitting a string into words and counting them in parallel](https://juliafolds.github.io/Transducers.jl/stable/tutorials/words/)", 31 | "[FLoops.jl / Parallel loops](https://juliafolds.github.io/FLoops.jl/stable/tutorials/parallel/)", 32 | "[FoldsCUDA.jl / Examples](https://juliafolds.github.io/FoldsCUDA.jl/dev/)", 33 | ], 34 | "howto" => [ 35 | "[FLoops.jl / How to write X in parallel](https://juliafolds.github.io/FLoops.jl/stable/howto/parallel/)", 36 | ], 37 | "reference" => [ 38 | "[FLoops.jl documentation](https://juliafolds.github.io/FLoops.jl/stable/)", 39 | "[Folds.jl documentation](https://juliafolds.github.io/Folds.jl/stable/)", 40 | "[ThreadsX.jl documentation](https://tkf.github.io/ThreadsX.jl/dev/)", 41 | "[Transducers.jl documentation](https://juliafolds.github.io/Transducers.jl/stable/)", 42 | "[See JuliaFolds organization for more packages](https://github.com/JuliaFolds)", 43 | ], 44 | ) 45 | 46 | title(path) = open(title, path) 47 | function title(io::IO) 48 | for ln in eachline(io) 49 | m = match(r"^# +(.*)", ln) 50 | if m !== nothing 51 | return m[1] 52 | end 53 | end 54 | end 55 | 56 | for (cdir, ctitle) in chapters 57 | files = readdir(joinpath(root, cdir)) 58 | files = filter(endswith(".md"), files) 59 | if (files0 = get(firsts, cdir, nothing)) !== nothing 60 | files = vcat( 61 | intersect(files0, files), 62 | setdiff(files, files0), 63 | ) 64 | end 65 | pages = Iterators.map(files) do path 66 | t = title(joinpath(root, cdir, path)) 67 | if t === nothing 68 | return nothing 69 | else 70 | stem = splitext(basename(path))[1] 71 | return "[$t]($cdir/$stem/)" 72 | end 73 | end 74 | pages = collect(Iterators.filter(!isnothing, pages)) 75 | append!(pages, map(x -> "🔗 $x", get(external, cdir, []))) 76 | 77 | println("* $ctitle") 78 | for link in pages 79 | println(" * ", link) 80 | end 81 | end 82 | end 83 | ``` 84 | 85 | \textoutput{globaltoc} 86 | -------------------------------------------------------------------------------- /src/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorials/concurrency-patterns.md: -------------------------------------------------------------------------------- 1 | # Concurrency patterns for controlled parallelisms 2 | 3 | \note{ 4 | If you are new to parallel programming in Julia, have a look at other tutorials 5 | such as 6 | [A quick introduction to data parallelism in Julia](../quick-introduction/) and 7 | [Efficient and safe approaches to mutation in data parallelism](../mutations/). 8 | This tutorial is an introduction to how to control the scheduling details of the 9 | parallel execution. But, the best approach is to 10 | [*not*](https://www.infoq.com/presentations/Thinking-Parallel-Programming/) 11 | think about such issues. 12 | } 13 | 14 | High-level data parallelism is the best starting point for writing parallel 15 | programs. However, it is sometimes required to control the parallelism in your 16 | program so that, e.g., the usages of the bounded resources like memory can be 17 | managed. This is where it is necessary to deal with 18 | [_concurrency_](https://blog.golang.org/waza-talk). Although there are a lot 19 | of concurrency primitives, `Channel` is the most versatile tool that Julia 20 | provides out-of-the-box. In this tutorial, we look at how to implement simple 21 | and useful patterns based on `Channel`. Some of these patterns are known as 22 | task-parallel [_algorithmic 23 | skeletons_](https://en.wikipedia.org/wiki/Algorithmic_skeleton) (or [_parallel 24 | skeletons_](https://link.springer.com/referenceworkentry/10.1007%2F978-0-387-09766-4_24)). 25 | 26 | \tableofcontents 27 | 28 | ## Worker pool 29 | 30 | Useful for: 31 | * Limiting the number of concurrent/parallel tasks. 32 | * Limiting the resource usage. 33 | 34 | Pattern: 35 | 36 | ```julia:workerpool-1 37 | using Base.Threads: @spawn 38 | 39 | function workerpool(work!, allocate, request; ntasks = Threads.nthreads()) 40 | @sync for _ in 1:ntasks 41 | @spawn allocate() do resource 42 | for input in request 43 | work!(input, resource) 44 | end 45 | end 46 | end 47 | end 48 | ``` 49 | 50 | ```plaintext 51 |  x7 .-----> work!(x1, resource1) 52 | / 53 | request / x9 54 | [..., x12, x11, x10] ------+--------> work!(x6, resource2) 55 | \ 56 | \ 57 | x8 `-----> work!(x4, resource3) 58 | ``` 59 | 60 | Note that `request` must define `iterate` that can be invoked from multiple 61 | tasks concurrently. For example, a `Channel` can be used in this pattern. The 62 | `allocate` function passed as the second argument is used for allocating and 63 | releasing resources. 64 | 65 | Following example computes `mean(rand(UInt8, 2^15))` using `/dev/urandom`: 66 | 67 | ```julia:workerpool-2 68 | # Prepare inputs to the worker pool 69 | results = Vector{Float64}(undef, 2^5) 70 | works = Channel{typeof(Ref(results, 1))}(Inf) 71 | for i in eachindex(results) 72 | put!(works, Ref(results, i)) 73 | end 74 | close(works) 75 | 76 | let buffer_length = 2^10 77 | 78 | # `allocate(body)` function allocates the resource and pass it to `body`: 79 | function allocate(body) 80 | open("/dev/urandom") do file 81 | buffer = Vector{UInt8}(undef, buffer_length) 82 | body((file, buffer)) 83 | end 84 | end 85 | 86 | # The first argument to `workerpool` is a function that takes a work and a 87 | # resource: 88 | workerpool(allocate, works) do ref, (file, buffer) 89 | read!(file, buffer) 90 | ref[] = sum(buffer; init = 0.0) 91 | end 92 | 93 | sum(results) / (length(results) * buffer_length) 94 | end 95 | ``` 96 | 97 | \show{workerpool-2} 98 | 99 | ### Re-distribution hacks 100 | 101 | As of version 1.6, Julia's parallel task runtime does not migrate tasks across 102 | worker threads once a task is started. Thus, depending on when the worker pool 103 | is constructed, the above code many not distribute the tasks across worker 104 | threads. If there is no need to allocate the resource for each worker, the best 105 | solution is to use [`Threads.foreach(_, 106 | ::Channel)`](https://docs.julialang.org/en/v1/base/multi-threading/#Base.Threads.foreach). 107 | added in Julia 1.6, if you don't need to run `resource = allocate()` as above. 108 | 109 | If you need to allocate the resource, a simple workaround to this problem is to 110 | spawn a new task for each call to `work!`. This is the strategy used in 111 | `Threads.foreach`. 112 | 113 | ```julia:workerpool_redist 114 | function workerpool_redist(work!, allocate, request; kwargs...) 115 | workerpool(allocate, request; kwargs...) do input, resource 116 | wait(@spawn work!(input, resource)) 117 | end 118 | end 119 | ``` 120 | 121 | Another (less recommended) approach is to let `@threads` distribute the tasks 122 | 123 | ```julia 124 | @sync @threads for _ in 1:ntasks 125 | allocate() do resource 126 | @async for input in request 127 | work!(input, resource) 128 | end 129 | end 130 | end 131 | ``` 132 | 133 | This approach is not recommended because (1) how `@threads for` schedules the 134 | tasks is an implementation detail and (2) use of `@async` impedes migration of 135 | the tasks across OS threads (which is not implemented as of Julia 1.6 but is 136 | likely to be implemented in the future Julia versions). 137 | 138 | ## Task farm 139 | 140 | Useful for: 141 | * Limiting resources like worker pool. 142 | * Passing outputs into downstream processing. 143 | * Chaining computations with different resource requirements. 144 | 145 | Pattern: 146 | 147 | ```julia 148 | ys = Channel() do ys 149 | @sync for _ in 1:ntasks 150 | @spawn for x in xs 151 | put!(ys, f(x)) 152 | end 153 | end 154 | end 155 | ``` 156 | 157 | ```plaintext 158 |  x7 .------ f -------. f(x4) 159 | / \ 160 | / x9 f(x6) \ 161 | [..., x12, x11, x10] ------+--------- f ----------+----------> [f(x1), f(x5), f(x2), ...] 162 | \ / 163 | \ / 164 | x8 `------ f -------` f(x3) 165 | ``` 166 | 167 | This is an extension of the worker pool pattern. It is useful for limiting the 168 | number of concurrent/parallel tasks. However, as the diagram above indicates, it 169 | does not preserve the ordering of input (hence `u` in `umap` for unordered): 170 | 171 | ```julia:umap-1 172 | umap(f, xs; kwargs...) = umap(f, Any, xs; kwargs...) 173 | function umap(f, TY::Type, xs::Channel; ntasks = Threads.nthreads(), buffersize = ntasks) 174 | return Channel{TY}(buffersize) do ys 175 | @sync for _ in 1:ntasks 176 | @spawn for x in xs 177 | put!(ys, f(x)) 178 | end 179 | end 180 | end 181 | end 182 | ``` 183 | 184 | Note that the input collection `xs` must support concurrent iteration. To 185 | support arbitrary input collection, we can automatically wrap it in a fallback 186 | implementation: 187 | 188 | ```julia:umap-2 189 | function umap(f, TY::Type, xs; kwargs...) 190 | @assert !(xs isa Channel) # hide 191 | ch = Channel{eltype(xs)}() do ch 192 | for x in xs 193 | put!(ch, x) 194 | end 195 | end 196 | return umap(f, TY, ch; kwargs...) 197 | end 198 | ``` 199 | 200 | This pattern is called the _task farm_ algorithmic skeleton. 201 | 202 | `umap` can be used like `Iterators.map` although the ordering is not preserved: 203 | 204 | ```julia:umap-3 205 | function slow_square(x) 206 | sleep(rand(0.01:0.01:0.3)) 207 | return x^2 208 | end 209 | 210 | ans = begin # hide 211 | collect(umap(slow_square, 1:10; ntasks = 5)) 212 | end # hide 213 | @assert sort(ans) == (1:10) .^ 2 # hide 214 | ans # hide 215 | ``` 216 | 217 | \show{umap-3} 218 | 219 | ## Pipeline 220 | 221 | An interesting use-case is to call `umap` with `ntasks = 1` but with a long 222 | chain of calls: 223 | 224 | ```julia 225 | a = umap(f, xs; ntasks = 1) 226 | b = umap(g, a; ntasks = 1) 227 | c = umap(h, b; ntasks = 1) 228 | ``` 229 | 230 | ```plaintext 231 | step items in a items in b items in c 232 | ------- -------------- -------------- ------------ 233 | 1 a1 = f(x1) 234 | 2 a2 = f(x2); b1 = g(a1) 235 | 3 a3 = f(x3); b2 = g(a2); c1 = h(b1) 236 | 4 a4 = f(x4); b3 = g(a3); c2 = h(b2) 237 | 5 a5 = f(x5); b4 = g(a4); c3 = h(b3) 238 | 6 a6 = f(x5); b5 = g(a4); c4 = h(b4) 239 | 7 a7 = f(x7); b6 = g(a4); c5 = h(b5) 240 | 8 a8 = f(x7); b7 = g(a5); c6 = h(b6) 241 | 9 b8 = g(a8); c7 = h(b7) 242 | 10 c8 = h(b7) 243 | ``` 244 | 245 | If `ntask = 1`, the ordering of the input is preserved in the output. It can 246 | be used to improve the performance as long as `buffersize > 0` and the length 247 | of the input is long enough. In this case, different functions (`f`, `g`, and 248 | `h` in the above example) can be evaluated in parallel. This pattern is called 249 | the _pipeline_ algorithmic skeleton. 250 | 251 | ## Promise (request-response) 252 | 253 | Useful for: 254 | * Limiting resources like worker pool. 255 | * Associating input and output. 256 | 257 | The task farm pattern has an unfortunate restriction that it does not preserve 258 | the ordering of input. Can we make it work when we want to relate the input and 259 | output? One approach is to combine [promise (or 260 | future)](https://en.wikipedia.org/wiki/Futures_and_promises) with the worker 261 | pool pattern. 262 | 263 | Similar to the worker pool pattern, we still use a channel as the request queue 264 | (`request`) that the worker waits for the works. The key trick here is to send 265 | another channel (`promise`) over the request channel together with the input 266 | describing the work. Once the worker finish the computation, it "returns" the 267 | result by putting it in the `promise` channel. 268 | 269 | ```julia:raw_service 270 | function raw_service(f; ntasks = Threads.nthreads()) 271 | request = Channel() do request 272 | @sync for _ in 1:ntasks 273 | @spawn for (x, promise) in request 274 | y = f(x) 275 | put!(promise, y) 276 | end 277 | end 278 | end 279 | return request 280 | end 281 | 282 | function call(request, x) 283 | promise = Channel(1) 284 | put!(request, (x, promise)) 285 | return take!(promise) 286 | end 287 | 288 | adder = raw_service() do x 289 | return x + 1 290 | end 291 | try 292 | @assert call(adder, 0) == 1 293 | @assert call(adder, 1) == 2 294 | finally 295 | close(adder) 296 | end 297 | ``` 298 | 299 | Several variants are possible: 300 | 301 | * If the input and the output type of `f` is known (say `X` and `Y`) we can use 302 | `Channel{Y}` as the `promise` channel and `Channel{Tuple{X,Channel{Y}}}` as 303 | the `request` channel. 304 | * If `f` needs some resources, it can be allocated once per worker. 305 | 306 | Another useful variant may be an asynchronous call API: 307 | 308 | ```julia 309 | function async_call(request, x) 310 | promise = Channel(1) 311 | put!(request, (x, promise)) 312 | return promise # no take! 313 | end 314 | ``` 315 | 316 | The caller can schedule the call and then retrieve the result (using `take!`) at 317 | different locations in the code. It is also useful for scheduling multiple 318 | concurrent (and potentially parallel) calls. 319 | 320 | ### Improving the API 321 | 322 | The above API using `raw_service` and `call` has a problem that the user must 323 | close the channel `adder` to cleanup the resources (tasks) deterministically and 324 | handle errors reliably. It's better to wrap our API so that it can be used with 325 | the `do` block pattern (c.f., `open(f, path)`) instead of explicit 326 | `try`-`finally`: 327 | 328 | ```julia:define_service 329 | function provide(body, request) 330 | endpoint(x) = call(request, x) 331 | try 332 | body(endpoint) 333 | finally 334 | close(request) 335 | end 336 | end 337 | 338 | function define_service(f; kwargs...) 339 | open_service(body) = provide(body, raw_service(f; kwargs...)) 340 | return open_service 341 | end 342 | ``` 343 | 344 | We can use this API in two steps: (1) define the serve and then (2) "open" it 345 | using another `do` block: 346 | 347 | ```julia:with_adder 348 | with_adder = define_service() do x 349 | return x + 1 350 | end 351 | with_adder() do add 352 | @assert add(0) == 1 353 | @assert add(1) == 2 354 | end 355 | ``` 356 | 357 | ### Wrapping single-thread API 358 | 359 | This pattern is useful also as an ad-hoc interface around an API that is not 360 | safe to call from arbitrary OS threads: 361 | 362 | ```julia:define_single_thread_service 363 | function define_single_thread_service(f) 364 | function open_service(body) 365 | request = Channel() do request 366 | for (x, promise) in request 367 | y = f(x) 368 | put!(promise, y) 369 | end 370 | end 371 | provide(body, request) 372 | end 373 | return open_service 374 | end 375 | 376 | with_adder = define_single_thread_service() do x 377 | return x + 1 # call a "thread-unsafe" API here 378 | end 379 | with_adder() do add 380 | @assert add(0) == 1 381 | @assert add(1) == 2 382 | end 383 | ``` 384 | 385 | This function makes sure (as of Julia 1.6) that `f` is called on the OS thread 386 | that `with_adder(body)` is called while `add(x)` can be called in any tasks. 387 | 388 | Note that the call to the "thread-unsafe" function `f` becomes the bottleneck if 389 | you use `define_single_thread_service(f)` because every worker has to wait for 390 | the preceding calls of `f`. It may be a reasonable approach when the execution 391 | time of `f` is much smaller than the parallelized portion of your program. 392 | However, if it is not the case, it may be better to switch to process-based 393 | parallelism (using Distributed.jl, Dagger.jl, etc.) instead of threading-based 394 | parallelism. 395 | 396 | ## See also 397 | 398 | * [JuliaActors/Actors.jl](https://github.com/JuliaActors/Actors.jl) 399 | (Concurrent computing in Julia based on the Actor Model) 400 | and other packages in 401 | * Julia implementations for the Example problems in Hoare's 1978 paper, 402 | "Communicating Sequential Processes" by Nathan Daly: 403 | 404 | -------------------------------------------------------------------------------- /src/tutorials/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/utils.jl: -------------------------------------------------------------------------------- 1 | const TEST_RESULT_PATH = joinpath(@__DIR__, ".test-result") 2 | write(TEST_RESULT_PATH, "") 3 | 4 | function lx_testcode(com, _) 5 | code = Franklin.content(com.braces[1]) 6 | return """ 7 | @@test_code 8 | @@title 🔬 Test Code@@ 9 | ```julia 10 | $code 11 | ``` 12 | @@ 13 | """ 14 | end 15 | 16 | function lx_testcheck(com, _) 17 | testname = Franklin.content(com.braces[1]) 18 | respath = joinpath(@__DIR__, "__site", "-test-", "output", testname * ".res") 19 | outpath = joinpath(@__DIR__, "__site", "-test-", "output", testname * ".out") 20 | result = read(respath, String) 21 | ok = result == "OK" 22 | if !ok 23 | @error( 24 | "Test `$testname` failed!", 25 | result = Text(result), 26 | output = Text(read(outpath, String)) 27 | ) 28 | write(TEST_RESULT_PATH, "failed") 29 | end 30 | if ok 31 | return """ 32 | @@test_ok 33 | @@title ☑ Pass@@ 34 | \\output{/-test-/$testname} 35 | @@ 36 | """ 37 | else 38 | return """ 39 | @@test_failure 40 | @@title ⚠ Failure@@ 41 | \\show{/-test-/$testname} 42 | @@ 43 | """ 44 | end 45 | end 46 | --------------------------------------------------------------------------------