├── .github
└── workflows
│ ├── doc.yml
│ └── test.yml
├── Cargo.lock
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── README.md
├── bench-scripts
├── circ
│ ├── bench-long-queue.py
│ ├── bench.py
│ ├── experiment.sh
│ ├── legends.py
│ ├── plot-long-queue.py
│ ├── plot-map.py
│ └── plot-queue.py
├── hp-brcu
│ ├── bench-long-running.py
│ ├── bench.py
│ ├── experiment.sh
│ ├── legends.py
│ ├── plot-long-running.py
│ └── plot.py
└── hp-revisited
│ ├── bench-hp-trees.py
│ ├── bench-short-lists.py
│ ├── bench.py
│ ├── experiment.sh
│ ├── legends.py
│ ├── plot-hp-trees.py
│ ├── plot-short-lists.py
│ └── plot.py
├── docs
└── adding-your-smr.md
├── requirements.txt
├── rust-toolchain
├── smrs
├── cdrc
│ ├── Cargo.toml
│ └── src
│ │ ├── internal
│ │ ├── mod.rs
│ │ ├── smr
│ │ │ ├── ebr.rs
│ │ │ ├── ebr_impl
│ │ │ │ ├── atomic.rs
│ │ │ │ ├── collector.rs
│ │ │ │ ├── default.rs
│ │ │ │ ├── deferred.rs
│ │ │ │ ├── epoch.rs
│ │ │ │ ├── guard.rs
│ │ │ │ ├── internal.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── sync
│ │ │ │ │ ├── list.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── once_lock.rs
│ │ │ │ │ └── queue.rs
│ │ │ ├── hp.rs
│ │ │ ├── hp_impl
│ │ │ │ ├── domain.rs
│ │ │ │ ├── hazard.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── retire.rs
│ │ │ │ └── thread.rs
│ │ │ └── mod.rs
│ │ ├── smr_common.rs
│ │ └── utils.rs
│ │ ├── lib.rs
│ │ ├── strongs.rs
│ │ └── weaks.rs
├── circ
│ ├── Cargo.toml
│ └── src
│ │ ├── lib.rs
│ │ ├── smr
│ │ ├── ebr.rs
│ │ ├── ebr_impl
│ │ │ ├── atomic.rs
│ │ │ ├── collector.rs
│ │ │ ├── default.rs
│ │ │ ├── deferred.rs
│ │ │ ├── epoch.rs
│ │ │ ├── guard.rs
│ │ │ ├── internal.rs
│ │ │ ├── mod.rs
│ │ │ └── sync
│ │ │ │ ├── list.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── once_lock.rs
│ │ │ │ └── queue.rs
│ │ ├── hp.rs
│ │ ├── hp_impl
│ │ │ ├── domain.rs
│ │ │ ├── hazard.rs
│ │ │ ├── mod.rs
│ │ │ ├── retire.rs
│ │ │ └── thread.rs
│ │ └── mod.rs
│ │ ├── smr_common.rs
│ │ ├── strong.rs
│ │ ├── utils.rs
│ │ └── weak.rs
├── hp-brcu
│ ├── Cargo.toml
│ └── src
│ │ ├── deferred.rs
│ │ ├── epoch.rs
│ │ ├── handle.rs
│ │ ├── hazard.rs
│ │ ├── internal.rs
│ │ ├── lib.rs
│ │ ├── pointers.rs
│ │ ├── queue.rs
│ │ └── rollback.rs
├── hp-pp
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── domain.rs
│ │ ├── hazard.rs
│ │ ├── lib.rs
│ │ ├── retire.rs
│ │ ├── tag.rs
│ │ └── thread.rs
│ └── tests
│ │ ├── harris_list.rs
│ │ └── test.rs
├── nbr
│ ├── Cargo.toml
│ └── src
│ │ ├── block_bag.rs
│ │ ├── collector.rs
│ │ ├── lib.rs
│ │ ├── recovery.rs
│ │ └── stats.rs
└── vbr
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── src
├── bin
│ ├── cdrc-ebr-flush.rs
│ ├── cdrc-ebr.rs
│ ├── cdrc-hp.rs
│ ├── circ-ebr.rs
│ ├── circ-hp.rs
│ ├── double-link.rs
│ ├── ebr.rs
│ ├── hp-brcu.rs
│ ├── hp-pp.rs
│ ├── hp-rcu.rs
│ ├── hp.rs
│ ├── long-running.rs
│ ├── nbr.rs
│ ├── nr.rs
│ ├── pebr.rs
│ └── vbr.rs
├── config
│ ├── map.rs
│ └── mod.rs
├── ds_impl
│ ├── cdrc
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── double_link.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
│ ├── circ_ebr
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── double_link.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
│ ├── circ_hp
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── double_link.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
│ ├── ebr
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── double_link.rs
│ │ ├── elim_ab_tree.rs
│ │ ├── ellen_tree.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
│ ├── hp
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── double_link.rs
│ │ ├── elim_ab_tree.rs
│ │ ├── ellen_tree.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ ├── pointers.rs
│ │ └── skip_list.rs
│ ├── hp_brcu
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── elim_ab_tree.rs
│ │ ├── list.rs
│ │ ├── list_alter.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
│ ├── hp_pp
│ │ ├── bonsai_tree.rs
│ │ ├── ellen_tree.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
│ ├── mod.rs
│ ├── nbr
│ │ ├── concurrent_map.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ └── natarajan_mittal_tree.rs
│ ├── nr
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── double_link.rs
│ │ ├── elim_ab_tree.rs
│ │ ├── ellen_tree.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ ├── pointers.rs
│ │ └── skip_list.rs
│ ├── pebr
│ │ ├── bonsai_tree.rs
│ │ ├── concurrent_map.rs
│ │ ├── elim_ab_tree.rs
│ │ ├── ellen_tree.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ ├── shield_pool.rs
│ │ └── skip_list.rs
│ └── vbr
│ │ ├── concurrent_map.rs
│ │ ├── elim_ab_tree.rs
│ │ ├── list.rs
│ │ ├── michael_hash_map.rs
│ │ ├── mod.rs
│ │ ├── natarajan_mittal_tree.rs
│ │ └── skip_list.rs
├── lib.rs
└── utils.rs
└── test-scripts
├── sanitize-circ.sh
├── sanitize-elim.sh
├── sanitize-hp.sh
├── sanitize-hppp.sh
├── sanitize-hpsh.sh
├── stress-vbr.sh
├── test-hpsh.sh
├── test-skiplist.sh
└── test-vbr.sh
/.github/workflows/doc.yml:
--------------------------------------------------------------------------------
1 | name: Build Docs
2 |
3 | on: [push, pull_request]
4 |
5 | permissions:
6 | contents: read
7 | pages: write
8 | id-token: write
9 |
10 | jobs:
11 | build:
12 | name: Build
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout repository
17 | uses: actions/checkout@v4
18 |
19 | - name: Setup Rust
20 | uses: dtolnay/rust-toolchain@stable
21 |
22 | - name: Configure cache
23 | uses: Swatinem/rust-cache@v2
24 |
25 | - name: Setup pages
26 | id: pages
27 | uses: actions/configure-pages@v4
28 |
29 | - name: Build docs
30 | run: |
31 | cargo clean --doc
32 | cargo doc --workspace --no-deps --lib
33 | cargo doc -p crossbeam-epoch --no-deps
34 | cargo doc -p crossbeam-pebr-epoch --no-deps
35 |
36 | # GitHub cannot find the nested index file by default.
37 | # We need to specify the target manually.
38 | - name: Add redirect
39 | run: echo '' > target/doc/index.html
40 |
41 | - name: Remove lock file
42 | run: rm target/doc/.lock
43 |
44 | - name: Upload artifact
45 | uses: actions/upload-pages-artifact@v3
46 | with:
47 | path: target/doc
48 |
49 | deploy:
50 | name: Deploy
51 | needs: build
52 | runs-on: ubuntu-latest
53 |
54 | environment:
55 | name: github-pages
56 | url: ${{ steps.deployment.outputs.page_url }}
57 |
58 | steps:
59 | - name: Deploy to GitHub Pages
60 | id: deployment
61 | uses: actions/deploy-pages@v4
62 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test Benchmark
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v3
11 | with:
12 | submodules: recursive
13 |
14 | - name: Install clang
15 | run: sudo apt-get update && sudo apt-get install -y clang
16 |
17 | - name: Install Rust nightly toolchain
18 | uses: actions-rs/toolchain@v1
19 | with:
20 | toolchain: nightly
21 | components: rustfmt, clippy
22 |
23 | - name: Install cargo-audit
24 | run: cargo install cargo-audit
25 |
26 | - name: Check code formatting
27 | run: cargo fmt -- --check
28 |
29 | - name: Run checks
30 | run: |
31 | cargo check --verbose
32 | cargo audit
33 |
34 | - name: Run tests
35 | run: |
36 | cargo test -- --nocapture --test-threads 1
37 | cargo test --release -- --nocapture --test-threads 1
38 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "./smrs/hp-pp",
4 | "./smrs/nbr",
5 | "./smrs/cdrc",
6 | "./smrs/hp-brcu",
7 | "./smrs/vbr",
8 | "./smrs/circ",
9 | ]
10 |
11 | [package]
12 | name = "smr-benchmark"
13 | version = "0.1.0"
14 | authors = ["authors"]
15 | edition = "2021"
16 | description = "SMR Benchmark: A Microbenchmark Suite for Concurrent Safe Memory Reclamation Schemes"
17 | repository = "https://github.com/kaist-cp/smr-benchmark"
18 | readme = "README.md"
19 |
20 | [dependencies]
21 | bitflags = "2.5"
22 | cfg-if = "1.0"
23 | clap = { version = "4.5.4", features = ["derive", "string"] }
24 | crossbeam-utils = "0.8"
25 | csv = "1.3.0"
26 | rand = "0.8"
27 | typenum = "1.17"
28 | num = "0.4.3"
29 | arrayvec = "0.7.6"
30 | scopeguard = "1"
31 | hp_pp = { path = "./smrs/hp-pp" }
32 | nbr = { path = "./smrs/nbr" }
33 | cdrc = { path = "./smrs/cdrc" }
34 | hp-brcu = { path = "./smrs/hp-brcu" }
35 | vbr = { path = "./smrs/vbr" }
36 | circ = { path = "./smrs/circ" }
37 |
38 | [target.'cfg(target_os = "linux")'.dependencies]
39 | tikv-jemallocator = "0.5"
40 | tikv-jemalloc-ctl = "0.5"
41 |
42 | [dependencies.crossbeam-ebr]
43 | package = "crossbeam-epoch"
44 | git = "https://github.com/kaist-cp/crossbeam"
45 | branch = "smr-benchmark"
46 |
47 | [dependencies.crossbeam-pebr]
48 | package = "crossbeam-pebr-epoch"
49 | git = "https://github.com/kaist-cp/crossbeam"
50 | branch = "pebr"
51 |
52 | [profile.release]
53 | lto = true
54 | codegen-units = 1
55 |
56 | [profile.release-with-debug]
57 | inherits = "release"
58 | debug = true
59 |
60 | [profile.release-simple]
61 | inherits = "release"
62 | debug = true
63 | lto = false
64 | codegen-units = 16
65 |
66 | [features]
67 | sanitize = ["crossbeam-pebr/sanitize"]
68 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04
2 |
3 | COPY . /bench
4 | WORKDIR /bench
5 |
6 | RUN apt-get update && apt-get install -y \
7 | python3.10 \
8 | python3-pip \
9 | curl && \
10 | pip3 install -r requirements.txt && \
11 | rm -rf /var/lib/apt/lists/* && \
12 | (curl https://sh.rustup.rs -sSf | bash -s -- -y) && \
13 | (echo 'source $HOME/.cargo/env' >> $HOME/.bashrc) && \
14 | # Download dependencies and pre-build binaries
15 | ~/.cargo/bin/cargo build --release && \
16 | ~/.cargo/bin/cargo test --no-run --release
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 KAIST Concurrency & Parallelism Laboratory
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bench-scripts/circ/bench-long-queue.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import subprocess
4 | import os
5 |
6 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
7 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release")
8 |
9 | mms_queue = ['ebr', 'cdrc-ebr', 'circ-ebr']
10 | runs = 5
11 |
12 | if os.path.exists('.git'):
13 | subprocess.run(['git', 'submodule', 'update', '--init', '--recursive'])
14 | subprocess.run(['cargo', 'build', '--release'])
15 |
16 |
17 | def extract_interval(cmd):
18 | for i in range(len(cmd)):
19 | if cmd[i] == '-i' and i + 1 < len(cmd):
20 | return int(cmd[i + 1])
21 | return 10
22 |
23 | cmds = []
24 |
25 | for mm in mms_queue:
26 | for i in range(10, 61, 10):
27 | cmd = [os.path.join(BIN_PATH, "double-link"), '-m', mm, '-i', str(i), '-t', '64', '-o', os.path.join(RESULTS_PATH, 'double-link-long-running.csv')]
28 | cmds.append(cmd)
29 |
30 | print('number of configurations: ', len(cmds))
31 | print('estimated time: ', sum(map(extract_interval, cmds)) // 60, ' min *', runs, 'times')
32 |
33 | os.makedirs(RESULTS_PATH, exist_ok=True)
34 | failed = []
35 | for run in range(runs):
36 | for i, cmd in enumerate(cmds):
37 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
38 | try:
39 | subprocess.run(cmd, timeout=extract_interval(cmd) + 30)
40 | except subprocess.TimeoutExpired:
41 | print("timeout")
42 | failed.append(' '.join(cmd))
43 | except KeyboardInterrupt:
44 | if len(failed) > 0:
45 | print("====failed====")
46 | print("\n".join(failed))
47 | exit(0)
48 | except:
49 | failed.append(' '.join(cmd))
50 |
51 | if len(failed) > 0:
52 | print("====failed====")
53 | print("\n".join(failed))
54 |
--------------------------------------------------------------------------------
/bench-scripts/circ/bench.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import subprocess
4 | import os, argparse
5 |
6 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
7 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release")
8 |
9 | dss = ['h-list', 'hm-list', 'hhs-list', 'hash-map', 'nm-tree', 'skip-list']
10 | mms_map = ['nr', 'ebr', 'hp', 'circ-ebr', 'circ-hp', 'cdrc-ebr', 'cdrc-hp']
11 | mms_queue = ['nr', 'ebr', 'circ-ebr', 'cdrc-ebr', 'cdrc-ebr-flush']
12 | i = 10
13 | runs = 1
14 | gs = [0, 1, 2]
15 |
16 | t_step, t_end = 0, 0
17 | cpu_count = os.cpu_count()
18 | if not cpu_count or cpu_count <= 12:
19 | t_step, t_end = 2, 16
20 | elif cpu_count <= 24:
21 | t_step, t_end = 4, 32
22 | elif cpu_count <= 64:
23 | t_step, t_end = 8, 128
24 | else:
25 | t_step, t_end = 8, 192
26 |
27 | parser = argparse.ArgumentParser()
28 | parser.add_argument("-e", "--end", dest="end", type=int, default=t_end,
29 | help="the maximum number in a sequence of the number of threads")
30 | parser.add_argument("-t", "--step", dest="step", type=int, default=t_step,
31 | help="the interval between adjacent pair in a sequence of the number of threads")
32 | args = parser.parse_args()
33 | t_end = args.end
34 | t_step = args.step
35 |
36 | ts_map = list(map(str, [1] + list(range(t_step, t_end + 1, t_step))))
37 | ts_queue = list(map(str, [1] + list(range(t_step, t_end + 1, t_step))))
38 |
39 | if os.path.exists('.git'):
40 | subprocess.run(['git', 'submodule', 'update', '--init', '--recursive'])
41 | subprocess.run(['cargo', 'build', '--release'])
42 |
43 | def key_ranges(ds):
44 | if ds in ["h-list", "hm-list", "hhs-list"]:
45 | return ["1000", "10000"]
46 | else:
47 | # 100K and 100M
48 | return ["100000", "100000000"]
49 |
50 | def invalid(mm, ds, g):
51 | is_invalid = False
52 | if ds == 'hhs-list':
53 | is_invalid |= g == 0 # HHSList is just HList with faster get()
54 | if mm == 'hp':
55 | is_invalid |= ds in ["h-list", "hhs-list", "nm-tree"]
56 | return is_invalid
57 |
58 | cmds = []
59 | estimated_time = 0
60 |
61 | for ds in dss:
62 | for mm in mms_map:
63 | for g in gs:
64 | if invalid(mm, ds, g):
65 | continue
66 | for t in ts_map:
67 | for kr in key_ranges(ds):
68 | cmd = [os.path.join(BIN_PATH, mm), '-i', str(i), '-d', ds, '-g', str(g), '-t', t, '-r', str(kr), '-o', os.path.join(RESULTS_PATH, f'{ds}.csv')]
69 | cmds.append(cmd)
70 | estimated_time += i * (1.1 if int(kr) <= 100000 else 1.5)
71 |
72 | for mm in mms_queue:
73 | for t in ts_queue:
74 | cmd = [os.path.join(BIN_PATH, "double-link"), '-m', mm, '-i', str(i), '-t', str(t), '-o', os.path.join(RESULTS_PATH, 'double-link.csv')]
75 | cmds.append(cmd)
76 | estimated_time += i * 1.1
77 |
78 | print('number of configurations: ', len(cmds))
79 | print('estimated time: ', int(estimated_time) // 60, ' min *', runs, 'times')
80 |
81 | os.makedirs(RESULTS_PATH, exist_ok=True)
82 | failed = []
83 | for run in range(runs):
84 | for i, cmd in enumerate(cmds):
85 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
86 | try:
87 | subprocess.run(cmd, timeout=i+30)
88 | except subprocess.TimeoutExpired:
89 | print("timeout")
90 | failed.append(' '.join(cmd))
91 | except KeyboardInterrupt:
92 | if len(failed) > 0:
93 | print("====failed====")
94 | print("\n".join(failed))
95 | exit(0)
96 | except:
97 | failed.append(' '.join(cmd))
98 |
99 | if len(failed) > 0:
100 | print("====failed====")
101 | print("\n".join(failed))
102 |
--------------------------------------------------------------------------------
/bench-scripts/circ/experiment.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | python3 ./bench.py "$@"
4 | python3 ./bench-long-queue.py
5 |
6 | python3 ./plot-map.py "$@"
7 | python3 ./plot-queue.py "$@"
8 | python3 ./plot-long-queue.py
9 |
--------------------------------------------------------------------------------
/bench-scripts/circ/legends.py:
--------------------------------------------------------------------------------
1 | import os
2 | import matplotlib.pyplot as plt
3 |
4 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
5 |
6 | color_triple = ["#E53629", "#2CD23E", "#4149C3"]
7 | face_alpha = "DF"
8 |
9 | EBR = "ebr"
10 | NR = "nr"
11 | HP = "hp"
12 | CDRC_EBR = "cdrc-ebr"
13 | CDRC_HP = "cdrc-hp"
14 | CIRC_EBR = "circ-ebr"
15 | CIRC_HP = "circ-hp"
16 | CDRC_EBR_FLUSH = "cdrc-ebr-flush"
17 |
18 | line_shapes = {
19 | NR: {
20 | "marker": ".",
21 | "color": "k",
22 | "linestyle": "-",
23 | },
24 | EBR: {
25 | "marker": "o",
26 | "color": color_triple[0],
27 | "markeredgewidth": 0.75,
28 | "markerfacecolor": color_triple[0] + face_alpha,
29 | "markeredgecolor": "k",
30 | "linestyle": "-",
31 | },
32 | CDRC_EBR: {
33 | "marker": "o",
34 | "color": color_triple[1],
35 | "markeredgewidth": 0.75,
36 | "markerfacecolor": color_triple[1] + face_alpha,
37 | "markeredgecolor": "k",
38 | "linestyle": "dotted",
39 | },
40 | CIRC_EBR: {
41 | "marker": "o",
42 | "color": color_triple[2],
43 | "markeredgewidth": 0.75,
44 | "markerfacecolor": color_triple[2] + face_alpha,
45 | "markeredgecolor": "k",
46 | "linestyle": "dashed",
47 | },
48 | HP: {
49 | "marker": "v",
50 | "color": color_triple[0],
51 | "markeredgewidth": 0.75,
52 | "markerfacecolor": color_triple[0] + face_alpha,
53 | "markeredgecolor": "k",
54 | "linestyle": "-",
55 | },
56 | CDRC_HP: {
57 | "marker": "v",
58 | "color": color_triple[1],
59 | "markeredgewidth": 0.75,
60 | "markerfacecolor": color_triple[1] + face_alpha,
61 | "markeredgecolor": "k",
62 | "linestyle": "dotted",
63 | },
64 | CIRC_HP: {
65 | "marker": "v",
66 | "color": color_triple[2],
67 | "markeredgewidth": 0.75,
68 | "markerfacecolor": color_triple[2] + face_alpha,
69 | "markeredgecolor": "k",
70 | "linestyle": "dashed",
71 | },
72 | CDRC_EBR_FLUSH: {
73 | "marker": "s",
74 | "color": "#828282",
75 | "markeredgewidth": 0.75,
76 | "markerfacecolor": "#828282" + face_alpha,
77 | "markeredgecolor": "k",
78 | "linestyle": "dashed",
79 | }
80 | }
81 |
82 | SMRS = [EBR, NR, HP, CDRC_EBR, CDRC_HP, CIRC_EBR, CIRC_HP, CDRC_EBR_FLUSH]
83 |
84 | os.makedirs(f'{RESULTS_PATH}/legends', exist_ok=True)
85 | for smr in SMRS:
86 | fig, ax = plt.subplots(ncols=1)
87 | ax.plot([0] * 3, linewidth=3, markersize=18, **line_shapes[smr])
88 | ax.set_xlim(2/3, 4/3)
89 | ax.set_axis_off()
90 | fig.set_size_inches(1.25, 0.75)
91 | fig.tight_layout()
92 | fig.savefig(f"{RESULTS_PATH}/legends/{smr}.pdf", bbox_inches="tight")
93 |
--------------------------------------------------------------------------------
/bench-scripts/circ/plot-long-queue.py:
--------------------------------------------------------------------------------
1 | # type: ignore
2 | import pandas as pd
3 | import warnings
4 | import os
5 | import matplotlib
6 | import matplotlib.pyplot as plt
7 |
8 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
9 | long_running_is = list(range(10, 61, 10))
10 | cpu_count = os.cpu_count()
11 | ts = [cpu_count//2, cpu_count, cpu_count*2]
12 |
13 | # raw column names
14 | THREADS = "threads"
15 | THROUGHPUT = "throughput"
16 | PEAK_MEM = "peak_mem"
17 | AVG_MEM = "avg_mem"
18 | INTERVAL = "interval"
19 |
20 | # legend
21 | SMR_ONLY = "SMR\n"
22 |
23 | EBR = "ebr"
24 | NR = "nr"
25 | HP = "hp"
26 | CDRC_EBR = "cdrc-ebr"
27 | CDRC_HP = "cdrc-hp"
28 | CIRC_EBR = "circ-ebr"
29 | CIRC_HP = "circ-hp"
30 |
31 | SMR_ONLYs = [EBR, CDRC_EBR, CIRC_EBR]
32 |
33 | color_triple = ["#E53629", "#2CD23E", "#4149C3"]
34 | face_alpha = "DF"
35 |
36 | line_shapes = {
37 | EBR: {
38 | "marker": "o",
39 | "color": color_triple[0],
40 | "markeredgewidth": 0.75,
41 | "markerfacecolor": color_triple[0] + face_alpha,
42 | "markeredgecolor": "k",
43 | "linestyle": "-",
44 | },
45 | CDRC_EBR: {
46 | "marker": "o",
47 | "color": color_triple[1],
48 | "markeredgewidth": 0.75,
49 | "markerfacecolor": color_triple[1] + face_alpha,
50 | "markeredgecolor": "k",
51 | "linestyle": "dotted",
52 | },
53 | CIRC_EBR: {
54 | "marker": "o",
55 | "color": color_triple[2],
56 | "markeredgewidth": 0.75,
57 | "markerfacecolor": color_triple[2] + face_alpha,
58 | "markeredgecolor": "k",
59 | "linestyle": "dashed",
60 | },
61 | }
62 |
63 | mm_order = {
64 | EBR: 1,
65 | CDRC_EBR: 2,
66 | CIRC_EBR: 4,
67 | }
68 |
69 | def draw(name, data, y_value, y_label):
70 | plt.figure(figsize=(5, 4))
71 |
72 | for mm in sorted(list(set(data.mm)), key=lambda mm: mm_order[mm]):
73 | d = data[data.mm == mm].sort_values(by=[INTERVAL], axis=0)
74 | plt.plot(d[INTERVAL], d[y_value],
75 | linewidth=3, markersize=15, **line_shapes[mm], zorder=30)
76 |
77 | plt.xlabel("Time interval to run (seconds)", fontsize=13)
78 | if y_label:
79 | plt.ylabel(y_label, fontsize=13)
80 | plt.yticks(fontsize=12)
81 | plt.xticks(fontsize=12)
82 | plt.grid(alpha=0.5)
83 | plt.savefig(name, bbox_inches='tight')
84 |
85 |
86 | if __name__ == '__main__':
87 | warnings.filterwarnings("ignore")
88 | pd.set_option('display.max_rows', None)
89 |
90 | # avoid Type 3 fonts
91 | matplotlib.rcParams['pdf.fonttype'] = 42
92 | matplotlib.rcParams['ps.fonttype'] = 42
93 |
94 | os.makedirs(f'{RESULTS_PATH}/queue-long-running', exist_ok=True)
95 |
96 | # preprocess (map)
97 | data = pd.read_csv(f'{RESULTS_PATH}/' + "double-link-long-running.csv")
98 | data.throughput = data.throughput.map(lambda x: x / 1_000_000)
99 | data.peak_mem = data.peak_mem.map(lambda x: x / (2 ** 30))
100 | data.avg_mem = data.avg_mem.map(lambda x: x / (2 ** 30))
101 |
102 | # take average of each runs
103 | avg = data.groupby(['mm', 'interval']).mean().reset_index()
104 | avg[SMR_ONLY] = pd.Categorical(avg.mm.map(str), SMR_ONLYs)
105 |
106 | y_label = 'Avg memory usage (GiB)'
107 | name = f'{RESULTS_PATH}/queue-long-running/long-running-queue_avg_mem.pdf'
108 | draw(name, avg, AVG_MEM, y_label)
109 |
--------------------------------------------------------------------------------
/bench-scripts/hp-brcu/bench-long-running.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 |
4 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
5 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release", "long-running")
6 |
7 | mms = ['nr', 'ebr', 'pebr', 'hp', 'hp-pp', 'nbr', 'nbr-large', 'hp-brcu', 'hp-rcu', 'vbr']
8 |
9 | krs = [(2 ** e) for e in range(18, 30, 1)]
10 | cpu_count = os.cpu_count()
11 | writers = cpu_count // 2
12 | readers = cpu_count // 2
13 | runs = 4
14 | i = 10
15 |
16 | if os.path.exists('.git'):
17 | subprocess.run(['git', 'submodule', 'update', '--init', '--recursive'])
18 | subprocess.run(['cargo', 'build', '--release', '--bin', 'long-running'])
19 |
20 | run_cmd = [BIN_PATH, f'-i{i}', f'-w{writers}', f'-g{readers}']
21 |
22 | cmds = []
23 |
24 | for mm in mms:
25 | for kr in krs:
26 | cmd = run_cmd + [f'-m{mm}', f'-r{kr}', f'-o{RESULTS_PATH}/long-running.csv']
27 | cmds.append(cmd)
28 |
29 | print('number of configurations: ', len(cmds))
30 | print('estimated time: ', (len(cmds) * i * 1.7) // 60, ' min *', runs, 'times')
31 |
32 | os.makedirs(RESULTS_PATH, exist_ok=True)
33 | failed = []
34 | for run in range(runs):
35 | for i, cmd in enumerate(cmds):
36 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
37 | try:
38 | # NOTE(`timeout=120`): prefilling may take a while...
39 | subprocess.run(cmd, timeout=120, check=True)
40 | except subprocess.TimeoutExpired:
41 | print("timeout")
42 | failed.append(' '.join(cmd))
43 | except KeyboardInterrupt:
44 | if len(failed) > 0:
45 | print("====failed====")
46 | print("\n".join(failed))
47 | exit(0)
48 | except:
49 | failed.append(' '.join(cmd))
50 |
51 | if len(failed) > 0:
52 | print("====failed====")
53 | print("\n".join(failed))
54 |
--------------------------------------------------------------------------------
/bench-scripts/hp-brcu/bench.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import subprocess
4 | import os
5 |
6 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
7 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release")
8 |
9 | dss = ['h-list', 'hm-list', 'hhs-list', 'hash-map', 'nm-tree', 'skip-list']
10 | # "-large" suffix if it uses a large garbage bag.
11 | mms = ['nr', 'ebr', 'pebr', 'hp', 'hp-pp', 'nbr', 'nbr-large', 'hp-brcu', 'vbr', 'hp-rcu']
12 | i = 10
13 | cpu_count = os.cpu_count()
14 | if not cpu_count or cpu_count <= 24:
15 | ts = list(map(str, [1] + list(range(4, 33, 4))))
16 | elif cpu_count <= 64:
17 | ts = list(map(str, [1] + list(range(8, 129, 8))))
18 | else:
19 | ts = list(map(str, [1] + list(range(12, 193, 12))))
20 | runs = 1
21 | gs = [0, 1, 2, 3]
22 |
23 | if os.path.exists('.git'):
24 | subprocess.run(['git', 'submodule', 'update', '--init', '--recursive'])
25 | subprocess.run(['cargo', 'build', '--release'])
26 |
27 | def key_ranges(ds):
28 | if ds in ["h-list", "hm-list", "hhs-list"]:
29 | # 1K and 10K
30 | return ["1000", "10000"]
31 | else:
32 | # 100K and 100M
33 | return ["100000", "100000000"]
34 |
35 | def is_suffix(orig, suf):
36 | return len(suf) <= len(orig) and mm[-len(suf):] == suf
37 |
38 | def make_cmd(mm, i, ds, g, t, kr):
39 | bag = "small"
40 | if is_suffix(mm, "-large"):
41 | mm = mm[:len(mm)-len("-large")]
42 | bag = "large"
43 |
44 | return [os.path.join(BIN_PATH, mm),
45 | '-i', str(i),
46 | '-d', str(ds),
47 | '-g', str(g),
48 | '-t', str(t),
49 | '-r', str(kr),
50 | '-b', bag,
51 | '-o', os.path.join(RESULTS_PATH, f'{ds}.csv')]
52 |
53 | def invalid(mm, ds, g):
54 | is_invalid = False
55 | if ds == 'hhs-list':
56 | is_invalid |= g == 0 # HHSList is just HList with faster get()
57 | if mm == 'hp':
58 | is_invalid |= ds in ["h-list", "hhs-list", "nm-tree"]
59 | if mm == 'nbr':
60 | is_invalid |= ds in ["hm-list", "skip-list"]
61 | return is_invalid
62 |
63 | cmds = []
64 |
65 | for ds in dss:
66 | for kr in key_ranges(ds):
67 | for mm in mms:
68 | for g in gs:
69 | if invalid(mm, ds, g):
70 | continue
71 | for t in ts:
72 | cmds.append(make_cmd(mm, i, ds, g, t, kr))
73 |
74 | print('number of configurations: ', len(cmds))
75 | print('estimated time: ', (len(cmds) * i * 1.1) // 60, ' min *', runs, 'times\n')
76 |
77 | for i, cmd in enumerate(cmds):
78 | try:
79 | print(f"\rdry-running commands... ({i+1}/{len(cmds)})", end="")
80 | subprocess.run(cmd + ['--dry-run'])
81 | except:
82 | print(f"A dry-run for the following command is failed:\n{' '.join(cmd)}")
83 | exit(1)
84 | print("\nAll dry-runs passed!\n")
85 |
86 | os.makedirs(RESULTS_PATH, exist_ok=True)
87 | failed = []
88 | for run in range(runs):
89 | for i, cmd in enumerate(cmds):
90 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
91 | try:
92 | subprocess.run(cmd, timeout=i+30)
93 | except subprocess.TimeoutExpired:
94 | print("timeout")
95 | failed.append(' '.join(cmd))
96 | except KeyboardInterrupt:
97 | if len(failed) > 0:
98 | print("====failed====")
99 | print("\n".join(failed))
100 | exit(0)
101 | except:
102 | failed.append(' '.join(cmd))
103 |
104 | if len(failed) > 0:
105 | print("====failed====")
106 | print("\n".join(failed))
107 |
--------------------------------------------------------------------------------
/bench-scripts/hp-brcu/experiment.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "======================================================================="
4 | echo "1. Throughputs & unreclaimed memory blocks on varying ratio of writes"
5 | echo "======================================================================="
6 |
7 | python3 ./bench.py
8 | python3 ./plot.py
9 |
10 | echo "======================================================================="
11 | echo "2. Throughputs on a long-running operations"
12 | echo "======================================================================="
13 |
14 | python3 ./bench-long-running.py
15 | python3 ./plot-long-running.py
16 |
--------------------------------------------------------------------------------
/bench-scripts/hp-brcu/legends.py:
--------------------------------------------------------------------------------
1 | import os
2 | import matplotlib.pyplot as plt
3 | import matplotlib.colors as colors
4 | from matplotlib.path import Path
5 | from matplotlib.transforms import Affine2D
6 |
7 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
8 |
9 | EBR = "ebr"
10 | PEBR = "pebr"
11 | NR = "nr"
12 | HP = "hp"
13 | HP_PP = "hp-pp"
14 | NBR = "nbr"
15 | NBR_LARGE = "nbr-large"
16 | HP_BRCU = "hp-brcu"
17 | HP_RCU = "hp-rcu"
18 | VBR = "vbr"
19 |
20 | SMRs = [NR, EBR, NBR, NBR_LARGE, HP_PP, HP, PEBR, HP_BRCU, HP_RCU, VBR]
21 |
22 | FACE_ALPHA = 0.85
23 |
24 | # https://matplotlib.org/stable/gallery/lines_bars_and_markers/marker_reference.html
25 | line_shapes = {
26 | NR: {
27 | "marker": ".",
28 | "color": "k",
29 | "linestyle": "-",
30 | },
31 | EBR: {
32 | "marker": "o",
33 | "color": "c",
34 | "linestyle": "-",
35 | },
36 | HP: {
37 | "marker": "v",
38 | "color": "hotpink",
39 | "linestyle": "dashed",
40 | },
41 | HP_PP: {
42 | "marker": "^",
43 | "color": "purple",
44 | "linestyle": "dashdot",
45 | },
46 | PEBR: {
47 | # Diamond("D") shape, but smaller.
48 | "marker": Path.unit_rectangle()
49 | .transformed(Affine2D().translate(-0.5, -0.5)
50 | .rotate_deg(45)),
51 | "color": "y",
52 | "linestyle": (5, (10, 3)),
53 | },
54 | NBR: {
55 | "marker": "p",
56 | "color": "blue",
57 | "linestyle": (0, (3, 1, 1, 1)),
58 | },
59 | NBR_LARGE: {
60 | "marker": "H",
61 | "color": "indigo",
62 | "linestyle": (0, (3, 1, 1, 1)),
63 | },
64 | HP_BRCU: {
65 | "marker": "X",
66 | "color": "r",
67 | "linestyle": (5, (10, 3)),
68 | },
69 | HP_RCU: {
70 | "marker": "P",
71 | "color": "green",
72 | "linestyle": (5, (10, 3)),
73 | },
74 | VBR: {
75 | "marker": "d",
76 | "color": "orange",
77 | "linestyle": (0, (2, 1)),
78 | },
79 | }
80 |
81 | # Add some common or derivable properties.
82 | line_shapes = dict(map(
83 | lambda kv: kv if kv[0] == NR else
84 | (kv[0], { **kv[1],
85 | "markerfacecolor": (*colors.to_rgb(kv[1]["color"]), FACE_ALPHA),
86 | "markeredgecolor": "k",
87 | "markeredgewidth": 0.75 }),
88 | line_shapes.items()
89 | ))
90 |
91 | if __name__ == "__main__":
92 | os.makedirs(f'{RESULTS_PATH}/legends', exist_ok=True)
93 | for smr in SMRs:
94 | fig, ax = plt.subplots(ncols=1)
95 | ax.plot([0] * 3, linewidth=3, markersize=18, **line_shapes[smr])
96 | ax.set_xlim(2/3, 4/3)
97 | ax.set_axis_off()
98 | fig.set_size_inches(1.25, 0.75)
99 | fig.tight_layout()
100 | fig.savefig(f"{RESULTS_PATH}/legends/{smr}.pdf", bbox_inches="tight")
101 |
--------------------------------------------------------------------------------
/bench-scripts/hp-revisited/bench-hp-trees.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import subprocess
4 | import os
5 |
6 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
7 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release")
8 |
9 | dss = ['nm-tree', 'efrb-tree']
10 | # "-large" suffix if it uses a large garbage bag.
11 | mms = ['hp']
12 | i = 10
13 | cpu_count = os.cpu_count()
14 | if not cpu_count or cpu_count <= 24:
15 | ts = list(map(str, [1] + list(range(4, 33, 4))))
16 | elif cpu_count <= 64:
17 | ts = list(map(str, [1] + list(range(8, 129, 8))))
18 | else:
19 | ts = list(map(str, [1] + list(range(12, 193, 12))))
20 | runs = 2
21 | gs = [0, 1, 2]
22 |
23 | subprocess.run(['cargo', 'build', '--release'])
24 |
25 | def key_ranges(ds):
26 | return ["100000"]
27 |
28 | def is_suffix(orig, suf):
29 | return len(suf) <= len(orig) and mm[-len(suf):] == suf
30 |
31 | def make_cmd(mm, i, ds, g, t, kr):
32 | bag = "small"
33 | if is_suffix(mm, "-large"):
34 | mm = mm[:len(mm)-len("-large")]
35 | bag = "large"
36 |
37 | return [os.path.join(BIN_PATH, mm),
38 | '-i', str(i),
39 | '-d', str(ds),
40 | '-g', str(g),
41 | '-t', str(t),
42 | '-r', str(kr),
43 | '-b', bag,
44 | '-o', os.path.join(RESULTS_PATH, f'{ds}.csv')]
45 |
46 | def invalid(mm, ds, g):
47 | is_invalid = False
48 | if ds == 'hhs-list':
49 | is_invalid |= g == 0 # HHSList is just HList with faster get()
50 | if mm == 'nbr':
51 | is_invalid |= ds in ["hm-list", "skip-list"]
52 | if ds == 'elim-ab-tree':
53 | is_invalid |= mm in ["pebr", "hp-pp", "vbr"]
54 | return is_invalid
55 |
56 | cmds = []
57 |
58 | for ds in dss:
59 | for kr in key_ranges(ds):
60 | for mm in mms:
61 | for g in gs:
62 | if invalid(mm, ds, g):
63 | continue
64 | for t in ts:
65 | cmds.append(make_cmd(mm, i, ds, g, t, kr))
66 |
67 | print('number of configurations: ', len(cmds))
68 | print('estimated time: ', (len(cmds) * i * 1.1) // 60, ' min *', runs, 'times\n')
69 |
70 | for i, cmd in enumerate(cmds):
71 | try:
72 | print(f"\rdry-running commands... ({i+1}/{len(cmds)})", end="")
73 | subprocess.run(cmd + ['--dry-run'])
74 | except:
75 | print(f"A dry-run for the following command is failed:\n{' '.join(cmd)}")
76 | exit(1)
77 | print("\nAll dry-runs passed!\n")
78 |
79 | os.makedirs(RESULTS_PATH, exist_ok=True)
80 | failed = []
81 | for run in range(runs):
82 | for i, cmd in enumerate(cmds):
83 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
84 | try:
85 | subprocess.run(cmd, timeout=i+30)
86 | except subprocess.TimeoutExpired:
87 | print("timeout")
88 | failed.append(' '.join(cmd))
89 | except KeyboardInterrupt:
90 | if len(failed) > 0:
91 | print("====failed====")
92 | print("\n".join(failed))
93 | exit(0)
94 | except:
95 | failed.append(' '.join(cmd))
96 |
97 | if len(failed) > 0:
98 | print("====failed====")
99 | print("\n".join(failed))
100 |
--------------------------------------------------------------------------------
/bench-scripts/hp-revisited/bench-short-lists.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import subprocess
4 | import os
5 |
6 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
7 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release")
8 |
9 | dss = ['hhs-list', 'hm-list']
10 | # "-large" suffix if it uses a large garbage bag.
11 | mms = ['hp', 'hp-pp']
12 | i = 10
13 | cpu_count = os.cpu_count()
14 | if not cpu_count or cpu_count <= 24:
15 | ts = list(map(str, [1] + list(range(4, 33, 4))))
16 | elif cpu_count <= 64:
17 | ts = list(map(str, [1] + list(range(8, 129, 8))))
18 | else:
19 | ts = list(map(str, [1] + list(range(12, 193, 12))))
20 | runs = 2
21 | gs = [0, 1, 2]
22 |
23 | subprocess.run(['cargo', 'build', '--release'])
24 |
25 | def key_ranges(ds):
26 | return ["16"]
27 |
28 | def is_suffix(orig, suf):
29 | return len(suf) <= len(orig) and mm[-len(suf):] == suf
30 |
31 | def make_cmd(mm, i, ds, g, t, kr):
32 | bag = "small"
33 | if is_suffix(mm, "-large"):
34 | mm = mm[:len(mm)-len("-large")]
35 | bag = "large"
36 |
37 | return [os.path.join(BIN_PATH, mm),
38 | '-i', str(i),
39 | '-d', str(ds),
40 | '-g', str(g),
41 | '-t', str(t),
42 | '-r', str(kr),
43 | '-b', bag,
44 | '-o', os.path.join(RESULTS_PATH, f'{ds}.csv')]
45 |
46 | def invalid(mm, ds, g):
47 | is_invalid = False
48 | if ds == 'hhs-list':
49 | is_invalid |= g == 0 # HHSList is just HList with faster get()
50 | if mm == 'nbr':
51 | is_invalid |= ds in ["hm-list", "skip-list"]
52 | if ds == 'elim-ab-tree':
53 | is_invalid |= mm in ["pebr", "hp-pp", "vbr"]
54 | return is_invalid
55 |
56 | cmds = []
57 |
58 | for ds in dss:
59 | for kr in key_ranges(ds):
60 | for mm in mms:
61 | for g in gs:
62 | if invalid(mm, ds, g):
63 | continue
64 | for t in ts:
65 | cmds.append(make_cmd(mm, i, ds, g, t, kr))
66 |
67 | print('number of configurations: ', len(cmds))
68 | print('estimated time: ', (len(cmds) * i * 1.1) // 60, ' min *', runs, 'times\n')
69 |
70 | for i, cmd in enumerate(cmds):
71 | try:
72 | print(f"\rdry-running commands... ({i+1}/{len(cmds)})", end="")
73 | subprocess.run(cmd + ['--dry-run'])
74 | except:
75 | print(f"A dry-run for the following command is failed:\n{' '.join(cmd)}")
76 | exit(1)
77 | print("\nAll dry-runs passed!\n")
78 |
79 | os.makedirs(RESULTS_PATH, exist_ok=True)
80 | failed = []
81 | for run in range(runs):
82 | for i, cmd in enumerate(cmds):
83 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
84 | try:
85 | subprocess.run(cmd, timeout=i+30)
86 | except subprocess.TimeoutExpired:
87 | print("timeout")
88 | failed.append(' '.join(cmd))
89 | except KeyboardInterrupt:
90 | if len(failed) > 0:
91 | print("====failed====")
92 | print("\n".join(failed))
93 | exit(0)
94 | except:
95 | failed.append(' '.join(cmd))
96 |
97 | if len(failed) > 0:
98 | print("====failed====")
99 | print("\n".join(failed))
100 |
--------------------------------------------------------------------------------
/bench-scripts/hp-revisited/bench.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import subprocess
4 | import os
5 |
6 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
7 | BIN_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "target", "release")
8 |
9 | dss = ['h-list', 'hm-list', 'hhs-list', 'hash-map', 'nm-tree', 'skip-list', 'elim-ab-tree']
10 | # "-large" suffix if it uses a large garbage bag.
11 | mms = ['nr', 'ebr', 'pebr', 'hp', 'hp-pp', 'hp-brcu', 'vbr']
12 | i = 10
13 | cpu_count = os.cpu_count()
14 | if not cpu_count or cpu_count <= 24:
15 | ts = list(map(str, [1] + list(range(4, 33, 4))))
16 | elif cpu_count <= 64:
17 | ts = list(map(str, [1] + list(range(8, 129, 8))))
18 | else:
19 | ts = list(map(str, [1] + list(range(12, 193, 12))))
20 | runs = 2
21 | gs = [0, 1, 2]
22 |
23 | subprocess.run(['cargo', 'build', '--release'])
24 |
25 | def key_ranges(ds):
26 | if ds in ["h-list", "hm-list", "hhs-list"]:
27 | # 1K and 10K
28 | return ["1000", "10000"]
29 | else:
30 | # 100K and 100M
31 | return ["100000", "100000000"]
32 |
33 | def is_suffix(orig, suf):
34 | return len(suf) <= len(orig) and mm[-len(suf):] == suf
35 |
36 | def make_cmd(mm, i, ds, g, t, kr):
37 | bag = "small"
38 | if is_suffix(mm, "-large"):
39 | mm = mm[:len(mm)-len("-large")]
40 | bag = "large"
41 |
42 | return [os.path.join(BIN_PATH, mm),
43 | '-i', str(i),
44 | '-d', str(ds),
45 | '-g', str(g),
46 | '-t', str(t),
47 | '-r', str(kr),
48 | '-b', bag,
49 | '-o', os.path.join(RESULTS_PATH, f'{ds}.csv')]
50 |
51 | def invalid(mm, ds, g):
52 | is_invalid = False
53 | if ds == 'hhs-list':
54 | is_invalid |= g == 0 # HHSList is just HList with faster get()
55 | if mm == 'nbr':
56 | is_invalid |= ds in ["hm-list", "skip-list"]
57 | if ds == 'elim-ab-tree':
58 | is_invalid |= mm in ["hp-pp"]
59 | return is_invalid
60 |
61 | cmds = []
62 |
63 | for ds in dss:
64 | for kr in key_ranges(ds):
65 | for mm in mms:
66 | for g in gs:
67 | if invalid(mm, ds, g):
68 | continue
69 | for t in ts:
70 | cmds.append(make_cmd(mm, i, ds, g, t, kr))
71 |
72 | print('number of configurations: ', len(cmds))
73 | print('estimated time: ', (len(cmds) * i * 1.1) // 60, ' min *', runs, 'times\n')
74 |
75 | for i, cmd in enumerate(cmds):
76 | try:
77 | print(f"\rdry-running commands... ({i+1}/{len(cmds)})", end="")
78 | subprocess.run(cmd + ['--dry-run'])
79 | except:
80 | print(f"A dry-run for the following command is failed:\n{' '.join(cmd)}")
81 | exit(1)
82 | print("\nAll dry-runs passed!\n")
83 |
84 | os.makedirs(RESULTS_PATH, exist_ok=True)
85 | failed = []
86 | for run in range(runs):
87 | for i, cmd in enumerate(cmds):
88 | print("run {}/{}, bench {}/{}: '{}'".format(run + 1, runs, i + 1, len(cmds), ' '.join(cmd)))
89 | try:
90 | subprocess.run(cmd, timeout=i+30)
91 | except subprocess.TimeoutExpired:
92 | print("timeout")
93 | failed.append(' '.join(cmd))
94 | except KeyboardInterrupt:
95 | if len(failed) > 0:
96 | print("====failed====")
97 | print("\n".join(failed))
98 | exit(0)
99 | except:
100 | failed.append(' '.join(cmd))
101 |
102 | if len(failed) > 0:
103 | print("====failed====")
104 | print("\n".join(failed))
105 |
--------------------------------------------------------------------------------
/bench-scripts/hp-revisited/experiment.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | python3 ./bench-scripts/hp-revisited/bench.py
4 | python3 ./bench-scripts/hp-revisited/bench-short-lists.py
5 | python3 ./bench-scripts/hp-revisited/bench-hp-trees.py
6 |
7 | python3 ./bench-scripts/hp-revisited/plot.py
8 | python3 ./bench-scripts/hp-revisited/plot-short-lists.py
9 | python3 ./bench-scripts/hp-revisited/plot-hp-trees.py
10 |
--------------------------------------------------------------------------------
/bench-scripts/hp-revisited/legends.py:
--------------------------------------------------------------------------------
1 | import os
2 | import matplotlib.pyplot as plt
3 | import matplotlib.colors as colors
4 | from matplotlib.path import Path
5 | from matplotlib.transforms import Affine2D
6 |
7 | RESULTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results")
8 |
9 | EBR = "ebr"
10 | PEBR = "pebr"
11 | NR = "nr"
12 | HP = "hp"
13 | HP_PP = "hp-pp"
14 | HP_BRCU = "hp-brcu"
15 | HP_RCU = "hp-rcu"
16 | VBR = "vbr"
17 |
18 | SMRs = [NR, EBR, HP_PP, HP, PEBR, HP_BRCU, HP_RCU, VBR]
19 |
20 | FACE_ALPHA = 0.85
21 |
22 | # https://matplotlib.org/stable/gallery/lines_bars_and_markers/marker_reference.html
23 | line_shapes = {
24 | NR: {
25 | "marker": ".",
26 | "color": "k",
27 | "linestyle": "-",
28 | },
29 | EBR: {
30 | "marker": "o",
31 | "color": "c",
32 | "linestyle": "-",
33 | },
34 | HP: {
35 | "marker": "v",
36 | "color": "hotpink",
37 | "linestyle": "dashed",
38 | },
39 | HP_PP: {
40 | "marker": "^",
41 | "color": "purple",
42 | "linestyle": "dashdot",
43 | },
44 | PEBR: {
45 | # Diamond("D") shape, but smaller.
46 | "marker": Path.unit_rectangle()
47 | .transformed(Affine2D().translate(-0.5, -0.5)
48 | .rotate_deg(45)),
49 | "color": "y",
50 | "linestyle": (5, (10, 3)),
51 | },
52 | HP_BRCU: {
53 | "marker": "X",
54 | "color": "r",
55 | "linestyle": (5, (10, 3)),
56 | },
57 | HP_RCU: {
58 | "marker": "P",
59 | "color": "green",
60 | "linestyle": (5, (10, 3)),
61 | },
62 | VBR: {
63 | "marker": "d",
64 | "color": "orange",
65 | "linestyle": (0, (2, 1)),
66 | },
67 | # Used in `plot-short-lists`
68 | "PESSIM_HP": {
69 | "marker": "v",
70 | "color": "#828282",
71 | "linestyle": "dotted",
72 | },
73 | }
74 |
75 | # Add some common or derivable properties.
76 | line_shapes = dict(map(
77 | lambda kv: kv if kv[0] == NR else
78 | (kv[0], { **kv[1],
79 | "markerfacecolor": (*colors.to_rgb(kv[1]["color"]), FACE_ALPHA),
80 | "markeredgecolor": "k",
81 | "markeredgewidth": 0.75 }),
82 | line_shapes.items()
83 | ))
84 |
85 | if __name__ == "__main__":
86 | os.makedirs(f'{RESULTS_PATH}/legends', exist_ok=True)
87 | for smr in SMRs:
88 | fig, ax = plt.subplots(ncols=1)
89 | ax.plot([0] * 3, linewidth=3, markersize=18, **line_shapes[smr])
90 | ax.set_xlim(2/3, 4/3)
91 | ax.set_axis_off()
92 | fig.set_size_inches(1.25, 0.75)
93 | fig.tight_layout()
94 | fig.savefig(f"{RESULTS_PATH}/legends/{smr}.pdf", bbox_inches="tight")
95 |
--------------------------------------------------------------------------------
/docs/adding-your-smr.md:
--------------------------------------------------------------------------------
1 | # Tl;dr
2 |
3 | To add a new benchmark in `smr-benchmark`, you need to complete the following three tasks:
4 |
5 | 1. (If necessary) Implement your SMR in `./smrs` and define its dependency in `./Cargo.toml`.
6 | 2. Implement data structures in `./src/ds_impl/`.
7 | 3. Write the benchmark driver (a standalone binary for your SMR) in `./src/bin/.rs`
8 |
9 | # Details
10 |
11 | ### Implementing Your SMR
12 |
13 | Implement your SMR as a package in `./smrs` and define its dependency in `./Cargo.toml`. Alternatively, you can directly specify the dependency in `Cargo.toml` using the crate.io registry or a GitHub repository.
14 |
15 | * [Example 1 (CIRC; Implemented manually)](https://github.com/kaist-cp/smr-benchmark/tree/main/smrs/circ)
16 | * [Example 2 (`crossbeam-epoch`; Imported from GitHub)](https://github.com/kaist-cp/smr-benchmark/blob/main/Cargo.toml\#L40-L43)
17 |
18 | ### Implementing Data Structures
19 |
20 | Implement data structures in `./src/ds_impl/`. Follow this simple convention for the directory structure:
21 |
22 | * Define a common trait for your `ConcurrentMap` implementation and its stress test `smoke` in `concurrent_map.rs` ([Example](https://github.com/kaist-cp/smr-benchmark/blob/main/src/ds\_impl/ebr/concurrent\_map.rs)).
23 | * Implement your data structures according to the predefined `ConcurrentMap` trait ([Example](https://github.com/kaist-cp/smr-benchmark/blob/main/src/ds\_impl/ebr/list.rs\#L387-L412)), and include a test function that invokes `smoke` internally ([Example](https://github.com/kaist-cp/smr-benchmark/blob/main/src/ds\_impl/ebr/list.rs\#L485-L498)).
24 |
25 | There is currently no convention for other types of data structures, such as queues.
26 |
27 | ### Writing the Benchmark Driver
28 |
29 | The benchmark driver for map data structures (a standalone binary for your SMR) is located at `./src/bin/.rs` ([Example](https://github.com/kaist-cp/smr-benchmark/blob/main/src/bin/ebr.rs)). This will mostly be boilerplate code, so you should be able to write it easily by referring to existing examples.
30 |
31 | Afterward, you can run a benchmark by:
32 |
33 | ```
34 | cargo run --release --bin -- \
35 | -d \
36 | -t \
37 | -g \
38 | -r \
39 | -i
40 | ```
41 |
42 | Please refer to `README.md` or `cargo run --bin -- -h`.
43 |
44 | # Small Notes
45 |
46 | * To compile the suite, an **x86 Ubuntu** machine is required.
47 | * This is because some SMRs depend on x86 Ubuntu (e.g., PEBR and HP++ use a modified `membarrier`, which is specifically optimized for x86 Ubuntu).
48 | * By removing these dependencies, you can compile and run the suite on other environments (e.g., AArch64 macOS).
49 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pyarrow==15.0.1
2 | pandas==2.2.1
3 | plotnine==0.13.1
4 |
--------------------------------------------------------------------------------
/rust-toolchain:
--------------------------------------------------------------------------------
1 | nightly-2024-05-04
2 |
--------------------------------------------------------------------------------
/smrs/cdrc/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cdrc"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | crossbeam-utils = "0.8"
10 | membarrier = { git = "https://github.com/jeehoonkang/membarrier-rs.git", branch = "smr-benchmark" }
11 | scopeguard = "1.1.0"
12 | static_assertions = "1.1.0"
13 | atomic = "0.5"
14 | cfg-if = "1.0"
15 | rustc-hash = "1.1.0"
16 | memoffset = "0.7"
17 |
18 | [dev-dependencies]
19 | rand = "0.8"
20 | bitflags = "2.4.0"
21 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/mod.rs:
--------------------------------------------------------------------------------
1 | mod smr;
2 | mod smr_common;
3 | mod utils;
4 |
5 | pub use smr::{ebr_impl, hp_impl, CsEBR, CsHP};
6 | pub use smr_common::{Acquired, Cs, RetireType};
7 | pub use utils::{Counted, EjectAction, Pointer, TaggedCnt};
8 |
9 | pub(crate) use utils::*;
10 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/ebr.rs:
--------------------------------------------------------------------------------
1 | use std::mem;
2 |
3 | use atomic::Ordering;
4 |
5 | use super::ebr_impl::{pin, Guard};
6 | use crate::internal::utils::Counted;
7 | use crate::internal::{Acquired, Cs, RetireType, TaggedCnt};
8 |
9 | /// A tagged pointer which is pointing a `CountedObjPtr`.
10 | ///
11 | /// We may want to use `crossbeam_ebr::Shared` as a `Acquired`,
12 | /// but trait interfaces can be complicated because `crossbeam_ebr::Shared`
13 | /// requires to specify a lifetime specifier.
14 | pub struct AcquiredEBR(TaggedCnt);
15 |
16 | impl Acquired for AcquiredEBR {
17 | #[inline(always)]
18 | fn as_ptr(&self) -> TaggedCnt {
19 | self.0
20 | }
21 |
22 | #[inline(always)]
23 | fn null() -> Self {
24 | Self(TaggedCnt::null())
25 | }
26 |
27 | #[inline(always)]
28 | fn is_null(&self) -> bool {
29 | self.0.is_null()
30 | }
31 |
32 | #[inline(always)]
33 | fn swap(p1: &mut Self, p2: &mut Self) {
34 | mem::swap(p1, p2);
35 | }
36 |
37 | #[inline(always)]
38 | fn eq(&self, other: &Self) -> bool {
39 | self.0 == other.0
40 | }
41 |
42 | #[inline]
43 | fn clear(&mut self) {
44 | self.0 = TaggedCnt::null();
45 | }
46 |
47 | #[inline]
48 | fn set_tag(&mut self, tag: usize) {
49 | self.0 = self.0.with_tag(tag);
50 | }
51 |
52 | #[inline]
53 | unsafe fn copy_to(&self, other: &mut Self) {
54 | other.0 = self.0;
55 | }
56 | }
57 |
58 | pub struct CsEBR {
59 | guard: Option,
60 | }
61 |
62 | impl From for CsEBR {
63 | #[inline(always)]
64 | fn from(guard: Guard) -> Self {
65 | Self { guard: Some(guard) }
66 | }
67 | }
68 |
69 | impl Cs for CsEBR {
70 | type RawShield = AcquiredEBR;
71 |
72 | #[inline(always)]
73 | fn new() -> Self {
74 | Self::from(pin())
75 | }
76 |
77 | #[inline(always)]
78 | fn create_object(obj: T) -> *mut Counted {
79 | let obj = Counted::new(obj);
80 | Box::into_raw(Box::new(obj))
81 | }
82 |
83 | #[inline(always)]
84 | fn reserve(&self, ptr: TaggedCnt, shield: &mut Self::RawShield) {
85 | *shield = AcquiredEBR(ptr);
86 | }
87 |
88 | #[inline(always)]
89 | fn protect_snapshot(
90 | &self,
91 | link: &atomic::Atomic>,
92 | shield: &mut Self::RawShield,
93 | ) -> bool {
94 | let ptr = link.load(Ordering::Acquire);
95 | if !ptr.is_null() && unsafe { ptr.deref() }.ref_count() == 0 {
96 | shield.clear();
97 | false
98 | } else {
99 | *shield = AcquiredEBR(ptr);
100 | true
101 | }
102 | }
103 |
104 | #[inline(always)]
105 | unsafe fn own_object(ptr: *mut Counted) -> Counted {
106 | *Box::from_raw(ptr)
107 | }
108 |
109 | #[inline(always)]
110 | unsafe fn retire(&self, ptr: *mut Counted, ret_type: RetireType) {
111 | debug_assert!(!ptr.is_null());
112 | let cnt = &mut *ptr;
113 | if let Some(guard) = &self.guard {
114 | guard.defer_unchecked(move || {
115 | let inner_guard = Self::unprotected();
116 | inner_guard.eject(cnt, ret_type);
117 | });
118 | } else {
119 | self.eject(cnt, ret_type);
120 | }
121 | }
122 |
123 | #[inline]
124 | unsafe fn without_epoch() -> Self {
125 | Self { guard: None }
126 | }
127 |
128 | #[inline]
129 | unsafe fn unprotected() -> Self {
130 | Self { guard: None }
131 | }
132 |
133 | #[inline]
134 | fn clear(&mut self) {
135 | if let Some(guard) = &mut self.guard {
136 | guard.repin_after(|| {});
137 | }
138 | }
139 |
140 | #[inline]
141 | fn eager_reclaim(&mut self) {
142 | if let Some(guard) = &mut self.guard {
143 | guard.repin_after(|| {});
144 | guard.flush();
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/ebr_impl/collector.rs:
--------------------------------------------------------------------------------
1 | /// Epoch-based garbage collector.
2 | use core::fmt;
3 | use core::sync::atomic::Ordering;
4 |
5 | use super::guard::Guard;
6 | use super::internal::{Global, Local};
7 | use super::Epoch;
8 | use std::sync::Arc;
9 |
10 | /// An epoch-based garbage collector.
11 | pub struct Collector {
12 | pub(crate) global: Arc,
13 | }
14 |
15 | unsafe impl Send for Collector {}
16 | unsafe impl Sync for Collector {}
17 |
18 | impl Default for Collector {
19 | fn default() -> Self {
20 | Self {
21 | global: Arc::new(Global::new()),
22 | }
23 | }
24 | }
25 |
26 | impl Collector {
27 | /// Creates a new collector.
28 | pub fn new() -> Self {
29 | Self::default()
30 | }
31 |
32 | /// Registers a new handle for the collector.
33 | pub fn register(&self) -> LocalHandle {
34 | Local::register(self)
35 | }
36 |
37 | /// Reads the global epoch, without issueing a fence.
38 | #[inline]
39 | pub fn global_epoch(&self) -> Epoch {
40 | self.global.epoch.load(Ordering::Relaxed)
41 | }
42 |
43 | /// Checks if the global queue is empty.
44 | pub fn is_global_queue_empty(&self) -> bool {
45 | self.global.is_global_queue_empty()
46 | }
47 | }
48 |
49 | impl Clone for Collector {
50 | /// Creates another reference to the same garbage collector.
51 | fn clone(&self) -> Self {
52 | Collector {
53 | global: self.global.clone(),
54 | }
55 | }
56 | }
57 |
58 | impl fmt::Debug for Collector {
59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 | f.pad("Collector { .. }")
61 | }
62 | }
63 |
64 | impl PartialEq for Collector {
65 | /// Checks if both handles point to the same collector.
66 | fn eq(&self, rhs: &Collector) -> bool {
67 | Arc::ptr_eq(&self.global, &rhs.global)
68 | }
69 | }
70 | impl Eq for Collector {}
71 |
72 | /// A handle to a garbage collector.
73 | pub struct LocalHandle {
74 | pub(crate) local: *const Local,
75 | }
76 |
77 | impl LocalHandle {
78 | /// Pins the handle.
79 | #[inline]
80 | pub fn pin(&self) -> Guard {
81 | unsafe { (*self.local).pin() }
82 | }
83 |
84 | /// Returns `true` if the handle is pinned.
85 | #[inline]
86 | pub fn is_pinned(&self) -> bool {
87 | unsafe { (*self.local).is_pinned() }
88 | }
89 |
90 | /// Returns the `Collector` associated with this handle.
91 | #[inline]
92 | pub fn collector(&self) -> &Collector {
93 | unsafe { (*self.local).collector() }
94 | }
95 | }
96 |
97 | impl Drop for LocalHandle {
98 | #[inline]
99 | fn drop(&mut self) {
100 | unsafe {
101 | Local::release_handle(&*self.local);
102 | }
103 | }
104 | }
105 |
106 | impl fmt::Debug for LocalHandle {
107 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 | f.pad("LocalHandle { .. }")
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/ebr_impl/default.rs:
--------------------------------------------------------------------------------
1 | //! The default garbage collector.
2 | //!
3 | //! For each thread, a participant is lazily initialized on its first use, when the current thread
4 | //! is registered in the default collector. If initialized, the thread's participant will get
5 | //! destructed on thread exit, which in turn unregisters the thread.
6 |
7 | use super::collector::{Collector, LocalHandle};
8 | use super::guard::Guard;
9 | use super::sync::once_lock::OnceLock;
10 |
11 | fn collector() -> &'static Collector {
12 | /// The global data for the default garbage collector.
13 | static COLLECTOR: OnceLock = OnceLock::new();
14 | COLLECTOR.get_or_init(Collector::new)
15 | }
16 |
17 | thread_local! {
18 | /// The per-thread participant for the default garbage collector.
19 | static HANDLE: LocalHandle = collector().register();
20 | }
21 |
22 | /// Pins the current thread.
23 | #[inline]
24 | pub fn pin() -> Guard {
25 | with_handle(|handle| handle.pin())
26 | }
27 |
28 | /// Returns `true` if the current thread is pinned.
29 | #[inline]
30 | pub fn is_pinned() -> bool {
31 | with_handle(|handle| handle.is_pinned())
32 | }
33 |
34 | /// Returns the default global collector.
35 | pub fn default_collector() -> &'static Collector {
36 | collector()
37 | }
38 |
39 | #[inline]
40 | fn with_handle(mut f: F) -> R
41 | where
42 | F: FnMut(&LocalHandle) -> R,
43 | {
44 | HANDLE
45 | .try_with(|h| f(h))
46 | .unwrap_or_else(|_| f(&collector().register()))
47 | }
48 |
49 | #[cfg(all(test, not(crossbeam_loom)))]
50 | mod tests {
51 | use crossbeam_utils::thread;
52 |
53 | #[test]
54 | fn pin_while_exiting() {
55 | struct Foo;
56 |
57 | impl Drop for Foo {
58 | fn drop(&mut self) {
59 | // Pin after `HANDLE` has been dropped. This must not panic.
60 | super::pin();
61 | }
62 | }
63 |
64 | thread_local! {
65 | static FOO: Foo = Foo;
66 | }
67 |
68 | thread::scope(|scope| {
69 | scope.spawn(|_| {
70 | // Initialize `FOO` and then `HANDLE`.
71 | FOO.with(|_| ());
72 | super::pin();
73 | // At thread exit, `HANDLE` gets dropped first and `FOO` second.
74 | });
75 | })
76 | .unwrap();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/ebr_impl/deferred.rs:
--------------------------------------------------------------------------------
1 | use core::fmt;
2 | use core::marker::PhantomData;
3 | use core::mem::{self, MaybeUninit};
4 | use core::ptr;
5 |
6 | /// Number of words a piece of `Data` can hold.
7 | ///
8 | /// Three words should be enough for the majority of cases. For example, you can fit inside it the
9 | /// function pointer together with a fat pointer representing an object that needs to be destroyed.
10 | const DATA_WORDS: usize = 3;
11 |
12 | /// Some space to keep a `FnOnce()` object on the stack.
13 | type Data = [usize; DATA_WORDS];
14 |
15 | /// A `FnOnce()` that is stored inline if small, or otherwise boxed on the heap.
16 | ///
17 | /// This is a handy way of keeping an unsized `FnOnce()` within a sized structure.
18 | pub(crate) struct Deferred {
19 | call: unsafe fn(*mut u8),
20 | data: MaybeUninit,
21 | _marker: PhantomData<*mut ()>, // !Send + !Sync
22 | }
23 |
24 | impl fmt::Debug for Deferred {
25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
26 | f.pad("Deferred { .. }")
27 | }
28 | }
29 |
30 | impl Deferred {
31 | /// Constructs a new `Deferred` from a `FnOnce()`.
32 | pub(crate) fn new(f: F) -> Self {
33 | let size = mem::size_of::();
34 | let align = mem::align_of::();
35 |
36 | unsafe {
37 | if size <= mem::size_of::() && align <= mem::align_of::() {
38 | let mut data = MaybeUninit::::uninit();
39 | ptr::write(data.as_mut_ptr().cast::(), f);
40 |
41 | unsafe fn call(raw: *mut u8) {
42 | let f: F = ptr::read(raw.cast::());
43 | f();
44 | }
45 |
46 | Deferred {
47 | call: call::,
48 | data,
49 | _marker: PhantomData,
50 | }
51 | } else {
52 | let b: Box = Box::new(f);
53 | let mut data = MaybeUninit::::uninit();
54 | ptr::write(data.as_mut_ptr().cast::>(), b);
55 |
56 | unsafe fn call(raw: *mut u8) {
57 | // It's safe to cast `raw` from `*mut u8` to `*mut Box`, because `raw` is
58 | // originally derived from `*mut Box`.
59 | let b: Box = ptr::read(raw.cast::>());
60 | (*b)();
61 | }
62 |
63 | Deferred {
64 | call: call::,
65 | data,
66 | _marker: PhantomData,
67 | }
68 | }
69 | }
70 | }
71 |
72 | /// Calls the function.
73 | #[inline]
74 | pub(crate) fn call(mut self) {
75 | let call = self.call;
76 | unsafe { call(self.data.as_mut_ptr().cast::()) };
77 | }
78 | }
79 |
80 | #[cfg(all(test, not(crossbeam_loom)))]
81 | mod tests {
82 | #![allow(clippy::drop_copy)]
83 |
84 | use super::Deferred;
85 | use core::hint::black_box;
86 | use std::cell::Cell;
87 |
88 | #[test]
89 | fn on_stack() {
90 | let fired = &Cell::new(false);
91 | let a = [0usize; 1];
92 |
93 | let d = Deferred::new(move || {
94 | black_box(a);
95 | fired.set(true);
96 | });
97 |
98 | assert!(!fired.get());
99 | d.call();
100 | assert!(fired.get());
101 | }
102 |
103 | #[test]
104 | fn on_heap() {
105 | let fired = &Cell::new(false);
106 | let a = [0usize; 10];
107 |
108 | let d = Deferred::new(move || {
109 | black_box(a);
110 | fired.set(true);
111 | });
112 |
113 | assert!(!fired.get());
114 | d.call();
115 | assert!(fired.get());
116 | }
117 |
118 | #[test]
119 | fn string() {
120 | let a = "hello".to_string();
121 | let d = Deferred::new(move || assert_eq!(a, "hello"));
122 | d.call();
123 | }
124 |
125 | #[test]
126 | fn boxed_slice_i32() {
127 | let a: Box<[i32]> = vec![2, 3, 5, 7].into_boxed_slice();
128 | let d = Deferred::new(move || assert_eq!(*a, [2, 3, 5, 7]));
129 | d.call();
130 | }
131 |
132 | #[test]
133 | fn long_slice_usize() {
134 | let a: [usize; 5] = [2, 3, 5, 7, 11];
135 | let d = Deferred::new(move || assert_eq!(a, [2, 3, 5, 7, 11]));
136 | d.call();
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/ebr_impl/sync/mod.rs:
--------------------------------------------------------------------------------
1 | //! Synchronization primitives.
2 |
3 | pub(crate) mod list;
4 | pub(crate) mod once_lock;
5 | pub(crate) mod queue;
6 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/ebr_impl/sync/once_lock.rs:
--------------------------------------------------------------------------------
1 | // Based on unstable std::sync::OnceLock.
2 | //
3 | // Source: https://github.com/rust-lang/rust/blob/8e9c93df464b7ada3fc7a1c8ccddd9dcb24ee0a0/library/std/src/sync/once_lock.rs
4 |
5 | use core::cell::UnsafeCell;
6 | use core::mem::MaybeUninit;
7 | use core::sync::atomic::{AtomicBool, Ordering};
8 | use std::sync::Once;
9 |
10 | pub(crate) struct OnceLock {
11 | once: Once,
12 | // Once::is_completed requires Rust 1.43, so use this to track of whether they have been initialized.
13 | is_initialized: AtomicBool,
14 | value: UnsafeCell>,
15 | // Unlike std::sync::OnceLock, we don't need PhantomData here because
16 | // we don't use #[may_dangle].
17 | }
18 |
19 | unsafe impl Sync for OnceLock {}
20 | unsafe impl Send for OnceLock {}
21 |
22 | impl OnceLock {
23 | /// Creates a new empty cell.
24 | #[must_use]
25 | pub(crate) const fn new() -> Self {
26 | Self {
27 | once: Once::new(),
28 | is_initialized: AtomicBool::new(false),
29 | value: UnsafeCell::new(MaybeUninit::uninit()),
30 | }
31 | }
32 |
33 | /// Gets the contents of the cell, initializing it with `f` if the cell
34 | /// was empty.
35 | ///
36 | /// Many threads may call `get_or_init` concurrently with different
37 | /// initializing functions, but it is guaranteed that only one function
38 | /// will be executed.
39 | ///
40 | /// # Panics
41 | ///
42 | /// If `f` panics, the panic is propagated to the caller, and the cell
43 | /// remains uninitialized.
44 | ///
45 | /// It is an error to reentrantly initialize the cell from `f`. The
46 | /// exact outcome is unspecified. Current implementation deadlocks, but
47 | /// this may be changed to a panic in the future.
48 | pub(crate) fn get_or_init(&self, f: F) -> &T
49 | where
50 | F: FnOnce() -> T,
51 | {
52 | // Fast path check
53 | if self.is_initialized() {
54 | // SAFETY: The inner value has been initialized
55 | return unsafe { self.get_unchecked() };
56 | }
57 | self.initialize(f);
58 |
59 | debug_assert!(self.is_initialized());
60 |
61 | // SAFETY: The inner value has been initialized
62 | unsafe { self.get_unchecked() }
63 | }
64 |
65 | #[inline]
66 | fn is_initialized(&self) -> bool {
67 | self.is_initialized.load(Ordering::Acquire)
68 | }
69 |
70 | #[cold]
71 | fn initialize(&self, f: F)
72 | where
73 | F: FnOnce() -> T,
74 | {
75 | let slot = self.value.get().cast::();
76 | let is_initialized = &self.is_initialized;
77 |
78 | self.once.call_once(|| {
79 | let value = f();
80 | unsafe {
81 | slot.write(value);
82 | }
83 | is_initialized.store(true, Ordering::Release);
84 | });
85 | }
86 |
87 | /// # Safety
88 | ///
89 | /// The value must be initialized
90 | unsafe fn get_unchecked(&self) -> &T {
91 | debug_assert!(self.is_initialized());
92 | &*self.value.get().cast::()
93 | }
94 | }
95 |
96 | impl Drop for OnceLock {
97 | fn drop(&mut self) {
98 | if self.is_initialized() {
99 | // SAFETY: The inner value has been initialized
100 | unsafe { self.value.get().cast::().drop_in_place() };
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/hp.rs:
--------------------------------------------------------------------------------
1 | use std::{mem::swap, ptr::null};
2 |
3 | use atomic::Ordering;
4 |
5 | use crate::{Acquired, Counted, Cs, TaggedCnt};
6 |
7 | use super::hp_impl::{HazardPointer, Thread, DEFAULT_THREAD};
8 |
9 | pub struct AcquiredHP {
10 | hazptr: HazardPointer,
11 | ptr: TaggedCnt,
12 | }
13 |
14 | impl Acquired for AcquiredHP {
15 | #[inline]
16 | fn clear(&mut self) {
17 | self.hazptr.reset_protection();
18 | self.ptr = TaggedCnt::null();
19 | }
20 |
21 | #[inline]
22 | fn as_ptr(&self) -> TaggedCnt {
23 | self.ptr
24 | }
25 |
26 | #[inline]
27 | fn set_tag(&mut self, tag: usize) {
28 | self.ptr = self.ptr.with_tag(tag);
29 | }
30 |
31 | #[inline]
32 | fn null() -> Self {
33 | Self {
34 | hazptr: HazardPointer::default(),
35 | ptr: TaggedCnt::null(),
36 | }
37 | }
38 |
39 | #[inline]
40 | fn is_null(&self) -> bool {
41 | self.ptr.is_null()
42 | }
43 |
44 | #[inline]
45 | fn swap(p1: &mut Self, p2: &mut Self) {
46 | HazardPointer::swap(&mut p1.hazptr, &mut p2.hazptr);
47 | swap(&mut p1.ptr, &mut p2.ptr);
48 | }
49 |
50 | #[inline]
51 | fn eq(&self, other: &Self) -> bool {
52 | self.ptr == other.ptr
53 | }
54 |
55 | #[inline]
56 | unsafe fn copy_to(&self, other: &mut Self) {
57 | other.ptr = self.ptr;
58 | other.hazptr.protect_raw(other.ptr.as_raw());
59 | membarrier::light_membarrier();
60 | }
61 | }
62 |
63 | pub struct CsHP {
64 | thread: *const Thread,
65 | }
66 |
67 | impl Cs for CsHP {
68 | type RawShield = AcquiredHP;
69 |
70 | #[inline]
71 | fn new() -> Self {
72 | let thread = DEFAULT_THREAD.with(|t| (&**t) as *const Thread);
73 | Self { thread }
74 | }
75 |
76 | #[inline]
77 | unsafe fn without_epoch() -> Self {
78 | Self::new()
79 | }
80 |
81 | #[inline]
82 | unsafe fn unprotected() -> Self {
83 | Self { thread: null() }
84 | }
85 |
86 | #[inline]
87 | fn create_object(obj: T) -> *mut crate::Counted {
88 | let obj = Counted::new(obj);
89 | Box::into_raw(Box::new(obj))
90 | }
91 |
92 | #[inline]
93 | fn reserve(&self, ptr: TaggedCnt, shield: &mut Self::RawShield) {
94 | shield.ptr = ptr;
95 | shield.hazptr.protect_raw(ptr.as_raw());
96 | membarrier::light_membarrier();
97 | }
98 |
99 | #[inline]
100 | fn protect_snapshot(
101 | &self,
102 | link: &atomic::Atomic>,
103 | shield: &mut Self::RawShield,
104 | ) -> bool {
105 | let mut ptr = link.load(Ordering::Relaxed);
106 | loop {
107 | shield.ptr = ptr;
108 | shield.hazptr.protect_raw(ptr.as_raw());
109 | membarrier::light_membarrier();
110 |
111 | let new_ptr = link.load(Ordering::Acquire);
112 | if new_ptr == ptr {
113 | break;
114 | }
115 | ptr = new_ptr;
116 | }
117 |
118 | if !ptr.is_null() && unsafe { ptr.deref() }.ref_count() == 0 {
119 | shield.clear();
120 | false
121 | } else {
122 | true
123 | }
124 | }
125 |
126 | #[inline]
127 | unsafe fn own_object(ptr: *mut Counted) -> Counted {
128 | *Box::from_raw(ptr)
129 | }
130 |
131 | #[inline]
132 | unsafe fn retire(&self, ptr: *mut Counted, ret_type: crate::RetireType) {
133 | debug_assert!(!ptr.is_null());
134 | let cnt = &mut *ptr;
135 | if let Some(thread) = self.thread.as_ref() {
136 | thread.defer(ptr, move || {
137 | let inner_guard = Self::new();
138 | inner_guard.eject(cnt, ret_type);
139 | });
140 | } else {
141 | self.eject(cnt, ret_type);
142 | }
143 | }
144 |
145 | #[inline]
146 | fn clear(&mut self) {
147 | // No-op for HP.
148 | }
149 |
150 | #[inline]
151 | fn eager_reclaim(&mut self) {
152 | if let Some(thread) = unsafe { self.thread.as_ref() } {
153 | thread.eager_reclaim();
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/hp_impl/domain.rs:
--------------------------------------------------------------------------------
1 | use core::sync::atomic::{AtomicUsize, Ordering};
2 |
3 | use crossbeam_utils::CachePadded;
4 | use rustc_hash::FxHashSet;
5 |
6 | use super::hazard::ThreadRecords;
7 | use super::retire::RetiredList;
8 | use super::thread::Thread;
9 |
10 | #[derive(Debug)]
11 | pub struct Domain {
12 | pub(crate) threads: CachePadded,
13 | pub(crate) retireds: CachePadded,
14 | pub(crate) num_garbages: CachePadded,
15 | }
16 |
17 | impl Domain {
18 | pub const fn new() -> Self {
19 | Self {
20 | threads: CachePadded::new(ThreadRecords::new()),
21 | retireds: CachePadded::new(RetiredList::new()),
22 | num_garbages: CachePadded::new(AtomicUsize::new(0)),
23 | }
24 | }
25 |
26 | pub fn collect_guarded_ptrs(&self, reclaimer: &Thread) -> FxHashSet<*mut u8> {
27 | self.threads
28 | .iter()
29 | .flat_map(|thread| thread.iter(reclaimer))
30 | .collect()
31 | }
32 |
33 | pub fn num_garbages(&self) -> usize {
34 | self.num_garbages.load(Ordering::Acquire)
35 | }
36 | }
37 |
38 | impl Drop for Domain {
39 | fn drop(&mut self) {
40 | for t in self.threads.iter() {
41 | assert!(t.available.load(Ordering::Relaxed))
42 | }
43 | while !self.retireds.is_empty() {
44 | let mut retireds = self.retireds.pop_all();
45 | for r in retireds.drain(..) {
46 | unsafe { r.call() };
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/hp_impl/mod.rs:
--------------------------------------------------------------------------------
1 | mod domain;
2 | mod hazard;
3 | mod retire;
4 | mod thread;
5 |
6 | pub use hazard::HazardPointer;
7 | pub use thread::set_counts_between_flush;
8 |
9 | use std::thread_local;
10 |
11 | use domain::Domain;
12 | pub use thread::Thread;
13 |
14 | pub static DEFAULT_DOMAIN: Domain = Domain::new();
15 |
16 | thread_local! {
17 | pub static DEFAULT_THREAD: Box = Box::new(Thread::new(&DEFAULT_DOMAIN));
18 | }
19 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/hp_impl/retire.rs:
--------------------------------------------------------------------------------
1 | use core::ptr;
2 | use core::sync::atomic::{AtomicPtr, Ordering};
3 | use std::mem::{self, MaybeUninit};
4 |
5 | const DATA_WORDS: usize = 3;
6 | type DeferredData = [usize; DATA_WORDS];
7 |
8 | #[derive(Debug, Clone, Copy)]
9 | pub(crate) struct Retired {
10 | pub(crate) ptr: *mut u8,
11 | data: MaybeUninit,
12 | call: unsafe fn(*mut u8),
13 | }
14 |
15 | // TODO: require in retire
16 | unsafe impl Send for Retired {}
17 |
18 | impl Retired {
19 | pub(crate) fn new(ptr: *mut u8, f: F) -> Self {
20 | let size = mem::size_of::();
21 | let align = mem::align_of::();
22 |
23 | unsafe {
24 | if size <= mem::size_of::() && align <= mem::align_of::() {
25 | let mut data = MaybeUninit::::uninit();
26 | ptr::write(data.as_mut_ptr().cast::(), f);
27 |
28 | unsafe fn call(raw: *mut u8) {
29 | let f: F = ptr::read(raw.cast::());
30 | f();
31 | }
32 |
33 | Self {
34 | ptr,
35 | data,
36 | call: call::,
37 | }
38 | } else {
39 | let b: Box = Box::new(f);
40 | let mut data = MaybeUninit::::uninit();
41 | ptr::write(data.as_mut_ptr().cast::>(), b);
42 |
43 | unsafe fn call(raw: *mut u8) {
44 | // It's safe to cast `raw` from `*mut u8` to `*mut Box`, because `raw` is
45 | // originally derived from `*mut Box`.
46 | let b: Box = ptr::read(raw.cast::>());
47 | (*b)();
48 | }
49 |
50 | Self {
51 | ptr,
52 | data,
53 | call: call::,
54 | }
55 | }
56 | }
57 | }
58 |
59 | pub(crate) unsafe fn call(mut self) {
60 | let call = self.call;
61 | unsafe { call(self.data.as_mut_ptr().cast::()) };
62 | }
63 | }
64 |
65 | #[derive(Debug)]
66 | pub(crate) struct RetiredList {
67 | head: AtomicPtr,
68 | }
69 |
70 | #[derive(Debug)]
71 | struct RetiredListNode {
72 | retireds: Vec,
73 | next: *const RetiredListNode,
74 | }
75 |
76 | impl RetiredList {
77 | pub(crate) const fn new() -> Self {
78 | Self {
79 | head: AtomicPtr::new(core::ptr::null_mut()),
80 | }
81 | }
82 |
83 | pub(crate) fn is_empty(&self) -> bool {
84 | self.head.load(Ordering::Acquire).is_null()
85 | }
86 |
87 | pub(crate) fn push(&self, retireds: Vec) {
88 | let new = Box::leak(Box::new(RetiredListNode {
89 | retireds,
90 | next: ptr::null_mut(),
91 | }));
92 |
93 | let mut head = self.head.load(Ordering::Relaxed);
94 | loop {
95 | new.next = head;
96 | match self
97 | .head
98 | .compare_exchange(head, new, Ordering::Release, Ordering::Relaxed)
99 | {
100 | Ok(_) => return,
101 | Err(head_new) => head = head_new,
102 | }
103 | }
104 | }
105 |
106 | pub(crate) fn pop_all(&self) -> Vec {
107 | let mut cur = self.head.swap(core::ptr::null_mut(), Ordering::Acquire);
108 | let mut retireds = Vec::new();
109 | while !cur.is_null() {
110 | let mut cur_box = unsafe { Box::from_raw(cur) };
111 | retireds.append(&mut cur_box.retireds);
112 | cur = cur_box.next.cast_mut();
113 | }
114 | retireds
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr/mod.rs:
--------------------------------------------------------------------------------
1 | mod ebr;
2 | pub mod ebr_impl;
3 | mod hp;
4 | pub mod hp_impl;
5 |
6 | pub use ebr::CsEBR;
7 | pub use hp::CsHP;
8 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/internal/smr_common.rs:
--------------------------------------------------------------------------------
1 | use atomic::Atomic;
2 |
3 | use crate::internal::utils::Counted;
4 | use crate::internal::utils::EjectAction;
5 | use crate::internal::utils::TaggedCnt;
6 |
7 | pub enum RetireType {
8 | DecrementStrongCount,
9 | DecrementWeakCount,
10 | Dispose,
11 | }
12 |
13 | /// A SMR-specific acquired pointer trait.
14 | ///
15 | /// In most cases such as EBR, IBR and Hyaline, Acquired is equivalent to a simple tagged
16 | /// pointer pointing a `Counted`.
17 | ///
18 | /// However, for some pointer-based SMR, `Acquired` should contain other information like an
19 | /// index of a hazard slot. For this reason, a type for acquired pointer must be SMR-dependent,
20 | /// and every SMR must provide some reasonable interfaces to access and manage this pointer.
21 | pub trait Acquired {
22 | fn clear(&mut self);
23 | fn as_ptr(&self) -> TaggedCnt;
24 | fn set_tag(&mut self, tag: usize);
25 | fn null() -> Self;
26 | fn is_null(&self) -> bool;
27 | fn swap(p1: &mut Self, p2: &mut Self);
28 | fn eq(&self, other: &Self) -> bool;
29 | unsafe fn copy_to(&self, other: &mut Self);
30 | }
31 |
32 | /// A SMR-specific critical section manager trait.
33 | ///
34 | /// We construct this `Cs` right before starting an operation,
35 | /// and drop(or `clear`) it after the operation.
36 | pub trait Cs {
37 | /// A SMR-specific acquired pointer trait
38 | ///
39 | /// For more information, read a comment on `Acquired`.
40 | type RawShield: Acquired;
41 |
42 | fn new() -> Self;
43 | unsafe fn without_epoch() -> Self;
44 | unsafe fn unprotected() -> Self;
45 | fn create_object(obj: T) -> *mut Counted;
46 | /// Creates a shield for the given pointer, assuming that `ptr` is already protected by a
47 | /// reference count.
48 | fn reserve(&self, ptr: TaggedCnt, shield: &mut Self::RawShield);
49 | fn protect_snapshot(
50 | &self,
51 | link: &Atomic>,
52 | shield: &mut Self::RawShield,
53 | ) -> bool;
54 | unsafe fn own_object(ptr: *mut Counted) -> Counted;
55 | unsafe fn retire(&self, ptr: *mut Counted, ret_type: RetireType);
56 | fn clear(&mut self);
57 | fn eager_reclaim(&mut self);
58 |
59 | #[inline]
60 | unsafe fn dispose(&self, cnt: &mut Counted) {
61 | debug_assert!(cnt.ref_count() == 0);
62 | cnt.dispose();
63 | if cnt.release_weak() {
64 | self.destroy(cnt);
65 | }
66 | }
67 |
68 | #[inline]
69 | unsafe fn destroy(&self, cnt: &mut Counted) {
70 | debug_assert!(cnt.ref_count() == 0);
71 | drop(Self::own_object(cnt));
72 | }
73 |
74 | /// Perform an eject action. This can correspond to any action that
75 | /// should be delayed until the ptr is no longer protected
76 | #[inline]
77 | unsafe fn eject(&self, cnt: &mut Counted, ret_type: RetireType) {
78 | match ret_type {
79 | RetireType::DecrementStrongCount => self.decrement_ref_cnt(cnt),
80 | RetireType::DecrementWeakCount => self.decrement_weak_cnt(cnt),
81 | RetireType::Dispose => self.dispose(cnt),
82 | }
83 | }
84 |
85 | #[inline]
86 | unsafe fn increment_ref_cnt(&self, cnt: &Counted) -> bool {
87 | cnt.add_ref()
88 | }
89 |
90 | #[inline]
91 | unsafe fn increment_weak_cnt(&self, cnt: &Counted) -> bool {
92 | cnt.add_weak()
93 | }
94 |
95 | #[inline]
96 | unsafe fn decrement_ref_cnt(&self, cnt: &mut Counted) {
97 | debug_assert!(cnt.ref_count() >= 1);
98 | let result = cnt.release_ref();
99 |
100 | match result {
101 | EjectAction::Nothing => {}
102 | EjectAction::Delay => self.retire(cnt, RetireType::Dispose),
103 | EjectAction::Destroy => self.destroy(cnt),
104 | }
105 | }
106 |
107 | #[inline]
108 | unsafe fn decrement_weak_cnt(&self, cnt: &mut Counted) {
109 | debug_assert!(cnt.weak_count() >= 1);
110 | if cnt.release_weak() {
111 | self.destroy(cnt);
112 | }
113 | }
114 |
115 | #[inline]
116 | unsafe fn delayed_decrement_ref_cnt(&self, cnt: &mut Counted) {
117 | debug_assert!(cnt.ref_count() >= 1);
118 | self.retire(cnt, RetireType::DecrementStrongCount);
119 | }
120 |
121 | #[inline]
122 | unsafe fn delayed_decrement_weak_cnt(&self, cnt: &mut Counted) {
123 | debug_assert!(cnt.weak_count() >= 1);
124 | self.retire(cnt, RetireType::DecrementWeakCount);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/smrs/cdrc/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod internal;
2 | mod strongs;
3 | mod weaks;
4 |
5 | pub use internal::*;
6 | pub use strongs::*;
7 | pub use weaks::*;
8 |
9 | #[inline]
10 | pub fn set_counts_between_flush_ebr(counts: usize) {
11 | internal::ebr_impl::set_bag_capacity(counts);
12 | }
13 |
14 | #[inline]
15 | pub fn set_counts_between_flush_hp(counts: usize) {
16 | internal::hp_impl::set_counts_between_flush(counts);
17 | }
18 |
--------------------------------------------------------------------------------
/smrs/circ/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "circ"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | crossbeam-utils = "0.8"
10 | membarrier = { git = "https://github.com/jeehoonkang/membarrier-rs.git", branch = "smr-benchmark" }
11 | scopeguard = "1.1.0"
12 | static_assertions = "1.1.0"
13 | atomic = "0.5"
14 | cfg-if = "1.0"
15 | rustc-hash = "1.1.0"
16 | memoffset = "0.7"
17 |
18 | [dev-dependencies]
19 | rand = "0.8"
20 | bitflags = "2.4.0"
21 |
--------------------------------------------------------------------------------
/smrs/circ/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![feature(cfg_sanitize)]
2 | mod smr;
3 | mod smr_common;
4 | mod strong;
5 | mod utils;
6 | mod weak;
7 |
8 | pub use smr::*;
9 | pub use smr_common::*;
10 | pub use strong::*;
11 | pub use utils::*;
12 | pub use weak::*;
13 |
14 | #[inline]
15 | pub fn set_counts_between_flush_ebr(counts: usize) {
16 | smr::ebr_impl::set_bag_capacity(counts);
17 | }
18 |
19 | #[inline]
20 | pub fn set_counts_between_flush_hp(counts: usize) {
21 | smr::hp_impl::set_counts_between_flush(counts);
22 | }
23 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/ebr_impl/default.rs:
--------------------------------------------------------------------------------
1 | //! The default garbage collector.
2 | //!
3 | //! For each thread, a participant is lazily initialized on its first use, when the current thread
4 | //! is registered in the default collector. If initialized, the thread's participant will get
5 | //! destructed on thread exit, which in turn unregisters the thread.
6 |
7 | use super::collector::{Collector, LocalHandle};
8 | use super::guard::Guard;
9 | use super::sync::once_lock::OnceLock;
10 |
11 | fn collector() -> &'static Collector {
12 | /// The global data for the default garbage collector.
13 | static COLLECTOR: OnceLock = OnceLock::new();
14 | COLLECTOR.get_or_init(Collector::new)
15 | }
16 |
17 | thread_local! {
18 | /// The per-thread participant for the default garbage collector.
19 | static HANDLE: LocalHandle = collector().register();
20 | }
21 |
22 | /// Pins the current thread.
23 | #[inline]
24 | pub fn pin() -> Guard {
25 | with_handle(|handle| handle.pin())
26 | }
27 |
28 | /// Returns `true` if the current thread is pinned.
29 | #[inline]
30 | pub fn is_pinned() -> bool {
31 | with_handle(|handle| handle.is_pinned())
32 | }
33 |
34 | /// Returns the default global collector.
35 | pub fn default_collector() -> &'static Collector {
36 | collector()
37 | }
38 |
39 | #[inline]
40 | fn with_handle(mut f: F) -> R
41 | where
42 | F: FnMut(&LocalHandle) -> R,
43 | {
44 | HANDLE
45 | .try_with(|h| f(h))
46 | .unwrap_or_else(|_| f(&collector().register()))
47 | }
48 |
49 | #[cfg(all(test, not(crossbeam_loom)))]
50 | mod tests {
51 | use crossbeam_utils::thread;
52 |
53 | #[test]
54 | fn pin_while_exiting() {
55 | struct Foo;
56 |
57 | impl Drop for Foo {
58 | fn drop(&mut self) {
59 | // Pin after `HANDLE` has been dropped. This must not panic.
60 | super::pin();
61 | }
62 | }
63 |
64 | thread_local! {
65 | static FOO: Foo = Foo;
66 | }
67 |
68 | thread::scope(|scope| {
69 | scope.spawn(|_| {
70 | // Initialize `FOO` and then `HANDLE`.
71 | FOO.with(|_| ());
72 | super::pin();
73 | // At thread exit, `HANDLE` gets dropped first and `FOO` second.
74 | });
75 | })
76 | .unwrap();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/ebr_impl/deferred.rs:
--------------------------------------------------------------------------------
1 | use core::fmt;
2 | use core::marker::PhantomData;
3 | use core::mem::{self, MaybeUninit};
4 | use core::ptr;
5 |
6 | /// Number of words a piece of `Data` can hold.
7 | ///
8 | /// Three words should be enough for the majority of cases. For example, you can fit inside it the
9 | /// function pointer together with a fat pointer representing an object that needs to be destroyed.
10 | const DATA_WORDS: usize = 3;
11 |
12 | /// Some space to keep a `FnOnce()` object on the stack.
13 | type Data = [usize; DATA_WORDS];
14 |
15 | /// A `FnOnce()` that is stored inline if small, or otherwise boxed on the heap.
16 | ///
17 | /// This is a handy way of keeping an unsized `FnOnce()` within a sized structure.
18 | pub(crate) struct Deferred {
19 | call: unsafe fn(*mut u8),
20 | data: MaybeUninit,
21 | _marker: PhantomData<*mut ()>, // !Send + !Sync
22 | }
23 |
24 | impl fmt::Debug for Deferred {
25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
26 | f.pad("Deferred { .. }")
27 | }
28 | }
29 |
30 | impl Deferred {
31 | /// Constructs a new `Deferred` from a `FnOnce()`.
32 | pub(crate) fn new(f: F) -> Self {
33 | let size = mem::size_of::();
34 | let align = mem::align_of::();
35 |
36 | unsafe {
37 | if size <= mem::size_of::() && align <= mem::align_of::() {
38 | let mut data = MaybeUninit::::uninit();
39 | ptr::write(data.as_mut_ptr().cast::(), f);
40 |
41 | unsafe fn call(raw: *mut u8) {
42 | let f: F = ptr::read(raw.cast::());
43 | f();
44 | }
45 |
46 | Deferred {
47 | call: call::,
48 | data,
49 | _marker: PhantomData,
50 | }
51 | } else {
52 | let b: Box = Box::new(f);
53 | let mut data = MaybeUninit::::uninit();
54 | ptr::write(data.as_mut_ptr().cast::>(), b);
55 |
56 | unsafe fn call(raw: *mut u8) {
57 | // It's safe to cast `raw` from `*mut u8` to `*mut Box`, because `raw` is
58 | // originally derived from `*mut Box`.
59 | let b: Box = ptr::read(raw.cast::>());
60 | (*b)();
61 | }
62 |
63 | Deferred {
64 | call: call::,
65 | data,
66 | _marker: PhantomData,
67 | }
68 | }
69 | }
70 | }
71 |
72 | /// Calls the function.
73 | #[inline]
74 | pub(crate) fn call(mut self) {
75 | let call = self.call;
76 | unsafe { call(self.data.as_mut_ptr().cast::()) };
77 | }
78 | }
79 |
80 | #[cfg(all(test, not(crossbeam_loom)))]
81 | mod tests {
82 | #![allow(clippy::drop_copy)]
83 |
84 | use super::Deferred;
85 | use core::hint::black_box;
86 | use std::cell::Cell;
87 |
88 | #[test]
89 | fn on_stack() {
90 | let fired = &Cell::new(false);
91 | let a = [0usize; 1];
92 |
93 | let d = Deferred::new(move || {
94 | black_box(a);
95 | fired.set(true);
96 | });
97 |
98 | assert!(!fired.get());
99 | d.call();
100 | assert!(fired.get());
101 | }
102 |
103 | #[test]
104 | fn on_heap() {
105 | let fired = &Cell::new(false);
106 | let a = [0usize; 10];
107 |
108 | let d = Deferred::new(move || {
109 | black_box(a);
110 | fired.set(true);
111 | });
112 |
113 | assert!(!fired.get());
114 | d.call();
115 | assert!(fired.get());
116 | }
117 |
118 | #[test]
119 | fn string() {
120 | let a = "hello".to_string();
121 | let d = Deferred::new(move || assert_eq!(a, "hello"));
122 | d.call();
123 | }
124 |
125 | #[test]
126 | fn boxed_slice_i32() {
127 | let a: Box<[i32]> = vec![2, 3, 5, 7].into_boxed_slice();
128 | let d = Deferred::new(move || assert_eq!(*a, [2, 3, 5, 7]));
129 | d.call();
130 | }
131 |
132 | #[test]
133 | fn long_slice_usize() {
134 | let a: [usize; 5] = [2, 3, 5, 7, 11];
135 | let d = Deferred::new(move || assert_eq!(a, [2, 3, 5, 7, 11]));
136 | d.call();
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/ebr_impl/sync/mod.rs:
--------------------------------------------------------------------------------
1 | //! Synchronization primitives.
2 |
3 | pub(crate) mod list;
4 | pub(crate) mod once_lock;
5 | pub(crate) mod queue;
6 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/ebr_impl/sync/once_lock.rs:
--------------------------------------------------------------------------------
1 | // Based on unstable std::sync::OnceLock.
2 | //
3 | // Source: https://github.com/rust-lang/rust/blob/8e9c93df464b7ada3fc7a1c8ccddd9dcb24ee0a0/library/std/src/sync/once_lock.rs
4 |
5 | use core::cell::UnsafeCell;
6 | use core::mem::MaybeUninit;
7 | use core::sync::atomic::{AtomicBool, Ordering};
8 | use std::sync::Once;
9 |
10 | pub(crate) struct OnceLock {
11 | once: Once,
12 | // Once::is_completed requires Rust 1.43, so use this to track of whether they have been initialized.
13 | is_initialized: AtomicBool,
14 | value: UnsafeCell>,
15 | // Unlike std::sync::OnceLock, we don't need PhantomData here because
16 | // we don't use #[may_dangle].
17 | }
18 |
19 | unsafe impl Sync for OnceLock {}
20 | unsafe impl Send for OnceLock {}
21 |
22 | impl OnceLock {
23 | /// Creates a new empty cell.
24 | #[must_use]
25 | pub(crate) const fn new() -> Self {
26 | Self {
27 | once: Once::new(),
28 | is_initialized: AtomicBool::new(false),
29 | value: UnsafeCell::new(MaybeUninit::uninit()),
30 | }
31 | }
32 |
33 | /// Gets the contents of the cell, initializing it with `f` if the cell
34 | /// was empty.
35 | ///
36 | /// Many threads may call `get_or_init` concurrently with different
37 | /// initializing functions, but it is guaranteed that only one function
38 | /// will be executed.
39 | ///
40 | /// # Panics
41 | ///
42 | /// If `f` panics, the panic is propagated to the caller, and the cell
43 | /// remains uninitialized.
44 | ///
45 | /// It is an error to reentrantly initialize the cell from `f`. The
46 | /// exact outcome is unspecified. Current implementation deadlocks, but
47 | /// this may be changed to a panic in the future.
48 | pub(crate) fn get_or_init(&self, f: F) -> &T
49 | where
50 | F: FnOnce() -> T,
51 | {
52 | // Fast path check
53 | if self.is_initialized() {
54 | // SAFETY: The inner value has been initialized
55 | return unsafe { self.get_unchecked() };
56 | }
57 | self.initialize(f);
58 |
59 | debug_assert!(self.is_initialized());
60 |
61 | // SAFETY: The inner value has been initialized
62 | unsafe { self.get_unchecked() }
63 | }
64 |
65 | #[inline]
66 | fn is_initialized(&self) -> bool {
67 | self.is_initialized.load(Ordering::Acquire)
68 | }
69 |
70 | #[cold]
71 | fn initialize(&self, f: F)
72 | where
73 | F: FnOnce() -> T,
74 | {
75 | let slot = self.value.get().cast::();
76 | let is_initialized = &self.is_initialized;
77 |
78 | self.once.call_once(|| {
79 | let value = f();
80 | unsafe {
81 | slot.write(value);
82 | }
83 | is_initialized.store(true, Ordering::Release);
84 | });
85 | }
86 |
87 | /// # Safety
88 | ///
89 | /// The value must be initialized
90 | unsafe fn get_unchecked(&self) -> &T {
91 | debug_assert!(self.is_initialized());
92 | &*self.value.get().cast::()
93 | }
94 | }
95 |
96 | impl Drop for OnceLock {
97 | fn drop(&mut self) {
98 | if self.is_initialized() {
99 | // SAFETY: The inner value has been initialized
100 | unsafe { self.value.get().cast::().drop_in_place() };
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/hp_impl/domain.rs:
--------------------------------------------------------------------------------
1 | use core::sync::atomic::{AtomicUsize, Ordering};
2 |
3 | use crossbeam_utils::CachePadded;
4 | use rustc_hash::FxHashSet;
5 |
6 | use super::hazard::ThreadRecords;
7 | use super::retire::{Pile, Retired};
8 | use super::thread::Thread;
9 |
10 | #[derive(Debug)]
11 | pub struct Domain {
12 | pub(crate) threads: CachePadded,
13 | pub(crate) retireds: CachePadded>>,
14 | pub(crate) num_garbages: CachePadded,
15 | }
16 |
17 | impl Domain {
18 | pub const fn new() -> Self {
19 | Self {
20 | threads: CachePadded::new(ThreadRecords::new()),
21 | retireds: CachePadded::new(Pile::new()),
22 | num_garbages: CachePadded::new(AtomicUsize::new(0)),
23 | }
24 | }
25 |
26 | pub fn collect_guarded_ptrs(&self, reclaimer: &Thread) -> FxHashSet<*mut u8> {
27 | self.threads
28 | .iter()
29 | .flat_map(|thread| thread.iter(reclaimer))
30 | .collect()
31 | }
32 |
33 | pub fn num_garbages(&self) -> usize {
34 | self.num_garbages.load(Ordering::Acquire)
35 | }
36 | }
37 |
38 | impl Drop for Domain {
39 | fn drop(&mut self) {
40 | for t in self.threads.iter() {
41 | assert!(t.available.load(Ordering::Relaxed))
42 | }
43 | while !self.retireds.is_empty() {
44 | let mut retireds = self.retireds.pop_all_flatten();
45 | for r in retireds.drain(..) {
46 | unsafe { r.call() };
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/hp_impl/mod.rs:
--------------------------------------------------------------------------------
1 | mod domain;
2 | mod hazard;
3 | mod retire;
4 | mod thread;
5 |
6 | pub use hazard::HazardPointer;
7 | pub use thread::set_counts_between_flush;
8 |
9 | use std::thread_local;
10 |
11 | use domain::Domain;
12 | pub use thread::Thread;
13 |
14 | pub static DEFAULT_DOMAIN: Domain = Domain::new();
15 |
16 | thread_local! {
17 | pub static DEFAULT_THREAD: Box = Box::new(Thread::new(&DEFAULT_DOMAIN));
18 | }
19 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/hp_impl/retire.rs:
--------------------------------------------------------------------------------
1 | use core::ptr;
2 | use core::sync::atomic::{AtomicPtr, Ordering};
3 | use std::mem::{self, MaybeUninit};
4 |
5 | const DATA_WORDS: usize = 3;
6 | type DeferredData = [usize; DATA_WORDS];
7 |
8 | #[derive(Debug, Clone, Copy)]
9 | pub(crate) struct Retired {
10 | pub(crate) ptr: *mut u8,
11 | data: MaybeUninit,
12 | call: unsafe fn(*mut u8),
13 | }
14 |
15 | // TODO: require in retire
16 | unsafe impl Send for Retired {}
17 |
18 | impl Retired {
19 | pub(crate) fn new(ptr: *mut u8, f: F) -> Self {
20 | let size = mem::size_of::();
21 | let align = mem::align_of::();
22 |
23 | unsafe {
24 | if size <= mem::size_of::() && align <= mem::align_of::() {
25 | let mut data = MaybeUninit::::uninit();
26 | ptr::write(data.as_mut_ptr().cast::(), f);
27 |
28 | unsafe fn call(raw: *mut u8) {
29 | let f: F = ptr::read(raw.cast::());
30 | f();
31 | }
32 |
33 | Self {
34 | ptr,
35 | data,
36 | call: call::,
37 | }
38 | } else {
39 | let b: Box = Box::new(f);
40 | let mut data = MaybeUninit::::uninit();
41 | ptr::write(data.as_mut_ptr().cast::>(), b);
42 |
43 | unsafe fn call(raw: *mut u8) {
44 | // It's safe to cast `raw` from `*mut u8` to `*mut Box`, because `raw` is
45 | // originally derived from `*mut Box`.
46 | let b: Box = ptr::read(raw.cast::>());
47 | (*b)();
48 | }
49 |
50 | Self {
51 | ptr,
52 | data,
53 | call: call::,
54 | }
55 | }
56 | }
57 | }
58 |
59 | pub(crate) unsafe fn call(mut self) {
60 | let call = self.call;
61 | unsafe { call(self.data.as_mut_ptr().cast::()) };
62 | }
63 | }
64 |
65 | #[derive(Debug)]
66 | pub(crate) struct Pile {
67 | head: AtomicPtr>,
68 | }
69 |
70 | #[derive(Debug)]
71 | struct PileNode {
72 | item: T,
73 | next: *const Self,
74 | }
75 |
76 | impl Pile {
77 | pub(crate) const fn new() -> Self {
78 | Self {
79 | head: AtomicPtr::new(core::ptr::null_mut()),
80 | }
81 | }
82 |
83 | pub(crate) fn is_empty(&self) -> bool {
84 | self.head.load(Ordering::Acquire).is_null()
85 | }
86 |
87 | pub(crate) fn push(&self, item: T) {
88 | let new = Box::leak(Box::new(PileNode {
89 | item,
90 | next: ptr::null_mut(),
91 | }));
92 |
93 | let mut head = self.head.load(Ordering::Relaxed);
94 | loop {
95 | new.next = head;
96 | match self
97 | .head
98 | .compare_exchange(head, new, Ordering::Release, Ordering::Relaxed)
99 | {
100 | Ok(_) => return,
101 | Err(head_new) => head = head_new,
102 | }
103 | }
104 | }
105 |
106 | pub(crate) fn pop_all(&self) -> Vec {
107 | let mut cur = self.head.swap(core::ptr::null_mut(), Ordering::Acquire);
108 | let mut popped = Vec::new();
109 | while !cur.is_null() {
110 | let cur_box = unsafe { Box::from_raw(cur) };
111 | popped.push(cur_box.item);
112 | cur = cur_box.next.cast_mut();
113 | }
114 | popped
115 | }
116 | }
117 |
118 | impl Pile> {
119 | pub(crate) fn pop_all_flatten(&self) -> Vec {
120 | let mut cur = self.head.swap(core::ptr::null_mut(), Ordering::Acquire);
121 | let mut popped = Vec::new();
122 | while !cur.is_null() {
123 | let mut cur_box = unsafe { Box::from_raw(cur) };
124 | popped.append(&mut cur_box.item);
125 | cur = cur_box.next.cast_mut();
126 | }
127 | popped
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr/mod.rs:
--------------------------------------------------------------------------------
1 | mod ebr;
2 | pub mod ebr_impl;
3 | mod hp;
4 | pub mod hp_impl;
5 |
6 | pub use ebr::CsEBR;
7 | pub use hp::CsHP;
8 |
9 | pub use ebr::*;
10 |
--------------------------------------------------------------------------------
/smrs/circ/src/smr_common.rs:
--------------------------------------------------------------------------------
1 | use atomic::Ordering;
2 |
3 | use crate::{GraphNode, RcInner, TaggedCnt};
4 |
5 | /// A SMR-specific acquired pointer trait.
6 | ///
7 | /// In most cases such as EBR, IBR and Hyaline, Acquired is equivalent to a simple tagged
8 | /// pointer pointing a `Counted`.
9 | ///
10 | /// However, for some pointer-based SMR, `Acquired` should contain other information like an
11 | /// index of a hazard slot. For this reason, a type for acquired pointer must be SMR-dependent,
12 | /// and every SMR must provide some reasonable interfaces to access and manage this pointer.
13 | pub trait Acquired {
14 | fn clear(&mut self);
15 | fn as_ptr(&self) -> TaggedCnt;
16 | fn set_tag(&mut self, tag: usize);
17 | fn null() -> Self;
18 | fn is_null(&self) -> bool;
19 | fn swap(p1: &mut Self, p2: &mut Self);
20 | fn eq(&self, other: &Self) -> bool;
21 | unsafe fn copy_to(&self, other: &mut Self);
22 | }
23 |
24 | pub trait Validatable {
25 | fn validate(&self) -> bool;
26 | fn ptr(&self) -> TaggedCnt;
27 | }
28 |
29 | /// A SMR-specific critical section manager trait.
30 | ///
31 | /// We construct this `Cs` right before starting an operation,
32 | /// and drop(or `clear`) it after the operation.
33 | pub trait Cs {
34 | /// A SMR-specific acquired pointer trait
35 | ///
36 | /// For more information, read a comment on `Acquired`.
37 | type RawShield: Acquired;
38 | type WeakGuard: Validatable;
39 |
40 | fn new() -> Self;
41 | unsafe fn unprotected() -> Self;
42 | fn create_object(obj: T, init_strong: u32) -> *mut RcInner;
43 | unsafe fn own_object(ptr: *mut RcInner) -> RcInner;
44 | /// Creates a shield for the given pointer, assuming that `ptr` is already protected by a
45 | /// reference count.
46 | fn reserve(&self, ptr: TaggedCnt, shield: &mut Self::RawShield);
47 | fn acquire(&self, load: F, shield: &mut Self::RawShield