Skip to content

Commit 8623b77

Browse files
authored
Refactor iterate_meta_bits (#1181)
This PR refactors the mechanism for visiting (reading and/or updating) side metadata in bulk, specifically the function `SideMetadataSpec::iterate_meta_bits`. The function now uses a single `FnMut` callback instead of two `Fn` callbacks. It uses the enum type `BitByteRange` to distinguish whole byte ranges from bit ranges in a byte. This allows the user to capture variables mutably in the callback. This also removes the `Cell` used in `find_prev_non_zero_value_fast`. The function is made non-recursive to improve the performance. Some test cases are added to test for corner cases. The function is moved to a dedicated `ranges` module and renamed to `break_bit_range`, for several reasons: - It was a method of `SideMetadataSpec`, but it does not access any member of `SideMetadataSpec`. - It needs a non-trivial amount of testing to get corner cases correct, especially after refactoring into a non-recursive function. - Related types and functions can be added to the `ranges` module in the future. - Breaking a range of bytes into a range of aligned words and unaligned bytes in the beginning and the end. It will be used by finding VO bits from internal pointers and finding all VO bits in a region (for heap traversal). - Breaking a range of bytes at chunk boundaries. It will be used by `bulk_update_metadata`.
1 parent f785236 commit 8623b77

File tree

11 files changed

+645
-241
lines changed

11 files changed

+645
-241
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ perf_counter = ["pfm"]
7373

7474
# This feature is only used for tests with MockVM.
7575
# CI scripts run those tests with this feature.
76-
mock_test = []
76+
mock_test = ["test_private"]
77+
78+
# This feature will expose some private functions for testings or benchmarking.
79+
test_private = []
7780

7881
# .github/scripts/ci-common.sh extracts features from the following part (including from comments).
7982
# So be careful when editing or adding stuff to the section below.

benches/main.rs

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,25 @@ use criterion::criterion_group;
22
use criterion::criterion_main;
33
use criterion::Criterion;
44

5-
// As we can only initialize one MMTk instance, we have to run each benchmark in a separate process.
6-
// So we only register one benchmark to criterion ('bench_main'), and based on the env var MMTK_BENCH,
7-
// we pick the right benchmark to run.
5+
#[cfg(all(feature = "mock_test", feature = "test_private"))]
6+
pub mod mock_bench;
87

9-
// The benchmark can be executed with the following command. The feature `mock_test` is required, as the tests use MockVM.
10-
// MMTK_BENCH=alloc cargo bench --features mock_test
11-
// MMTK_BENCH=sft cargo bench --features mock_test
8+
#[cfg(all(not(feature = "mock_test"), feature = "test_private"))]
9+
pub mod regular_bench;
1210

13-
// [Yi] I am not sure if these benchmarks are helpful any more after the MockVM refactoring. MockVM is really slow, as it
14-
// is accessed with a lock, and it dispatches every call to function pointers in a struct. These tests may use MockVM,
15-
// so they become slower as well. And the slowdown
16-
// from MockVM may hide the actual performance difference when we change the functions that are benchmarked.
17-
// We may want to improve the MockVM implementation so we can skip dispatching for benchmarking, or introduce another MockVM
18-
// implementation for benchmarking.
19-
// However, I will just keep these benchmarks here. If we find it not useful, and we do not plan to improve MockVM, we can delete
20-
// them.
21-
22-
#[cfg(feature = "mock_test")]
23-
mod mock_bench;
24-
25-
pub fn bench_main(_c: &mut Criterion) {
26-
#[cfg(feature = "mock_test")]
27-
match std::env::var("MMTK_BENCH") {
28-
Ok(bench) => match bench.as_str() {
29-
"alloc" => mock_bench::alloc::bench(_c),
30-
"internal_pointer" => mock_bench::internal_pointer::bench(_c),
31-
"sft" => mock_bench::sft::bench(_c),
32-
_ => panic!("Unknown benchmark {:?}", bench),
33-
},
34-
Err(_) => panic!("Need to name a benchmark by the env var MMTK_BENCH"),
35-
}
36-
37-
#[cfg(not(feature = "mock_test"))]
38-
{
39-
eprintln!("ERROR: Currently there are no benchmarks when the \"mock_test\" feature is not enabled.");
40-
std::process::exit(1);
11+
pub fn bench_main(c: &mut Criterion) {
12+
cfg_if::cfg_if! {
13+
if #[cfg(feature = "mock_test")] {
14+
// If the "mock_test" feature is enabled, we only run mock test.
15+
mock_bench::bench(c);
16+
} else if #[cfg(feature = "test_private")] {
17+
regular_bench::bench(c);
18+
} else {
19+
eprintln!("ERROR: Benchmarks in mmtk_core requires the test_priavte feature (implied by mock_test) to run.");
20+
eprintln!(" Rerun with `MMTK_BENCH=\"bench_name\" cargo bench --features mock_test` to run mock-test benchmarks.");
21+
eprintln!(" Rerun with `cargo bench --features test_private -- bench_name` to run other benchmarks.");
22+
std::process::exit(1);
23+
}
4124
}
4225
}
4326

benches/mock_bench/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
1+
use criterion::Criterion;
2+
13
pub mod alloc;
24
pub mod internal_pointer;
35
pub mod sft;
6+
7+
// As we can only initialize one MMTk instance, we have to run each benchmark in a separate process.
8+
// So we only register one benchmark to criterion ('bench_main'), and based on the env var MMTK_BENCH,
9+
// we pick the right benchmark to run.
10+
11+
// The benchmark can be executed with the following command. The feature `mock_test` is required, as the tests use MockVM.
12+
// MMTK_BENCH=alloc cargo bench --features mock_test
13+
// MMTK_BENCH=sft cargo bench --features mock_test
14+
15+
// [Yi] I am not sure if these benchmarks are helpful any more after the MockVM refactoring. MockVM is really slow, as it
16+
// is accessed with a lock, and it dispatches every call to function pointers in a struct. These tests may use MockVM,
17+
// so they become slower as well. And the slowdown
18+
// from MockVM may hide the actual performance difference when we change the functions that are benchmarked.
19+
// We may want to improve the MockVM implementation so we can skip dispatching for benchmarking, or introduce another MockVM
20+
// implementation for benchmarking.
21+
// However, I will just keep these benchmarks here. If we find it not useful, and we do not plan to improve MockVM, we can delete
22+
// them.
23+
24+
pub fn bench(c: &mut Criterion) {
25+
match std::env::var("MMTK_BENCH") {
26+
Ok(bench) => match bench.as_str() {
27+
"alloc" => alloc::bench(c),
28+
"internal_pointer" => internal_pointer::bench(c),
29+
"sft" => sft::bench(c),
30+
_ => panic!("Unknown benchmark {:?}", bench),
31+
},
32+
Err(_) => panic!("Need to name a benchmark by the env var MMTK_BENCH"),
33+
}
34+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! Benchmarks for bulk zeroing and setting.
2+
3+
use std::os::raw::c_void;
4+
5+
use criterion::Criterion;
6+
use mmtk::util::{constants::LOG_BITS_IN_WORD, test_private, Address};
7+
8+
fn allocate_aligned(size: usize) -> Address {
9+
let ptr = unsafe {
10+
std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align(size, size).unwrap())
11+
};
12+
Address::from_mut_ptr(ptr)
13+
}
14+
15+
const LINE_BYTES: usize = 256usize; // Match an Immix line size.
16+
const BLOCK_BYTES: usize = 32768usize; // Match an Immix block size.
17+
18+
// Asssume one-bit-per-word metadata (matching VO bits).
19+
const LINE_META_BYTES: usize = LINE_BYTES >> LOG_BITS_IN_WORD;
20+
const BLOCK_META_BYTES: usize = BLOCK_BYTES >> LOG_BITS_IN_WORD;
21+
22+
pub fn bench(c: &mut Criterion) {
23+
c.bench_function("bzero_bset_line", |b| {
24+
let start = allocate_aligned(LINE_META_BYTES);
25+
let end = start + LINE_META_BYTES;
26+
27+
b.iter(|| {
28+
test_private::set_meta_bits(start, 0, end, 0);
29+
test_private::zero_meta_bits(start, 0, end, 0);
30+
})
31+
});
32+
33+
c.bench_function("bzero_bset_line_memset", |b| {
34+
let start = allocate_aligned(LINE_META_BYTES);
35+
let end = start + LINE_META_BYTES;
36+
37+
b.iter(|| unsafe {
38+
libc::memset(start.as_mut_ref() as *mut c_void, 0xff, end - start);
39+
libc::memset(start.as_mut_ref() as *mut c_void, 0x00, end - start);
40+
})
41+
});
42+
43+
c.bench_function("bzero_bset_block", |b| {
44+
let start = allocate_aligned(BLOCK_META_BYTES);
45+
let end = start + BLOCK_META_BYTES;
46+
47+
b.iter(|| {
48+
test_private::set_meta_bits(start, 0, end, 0);
49+
test_private::zero_meta_bits(start, 0, end, 0);
50+
})
51+
});
52+
53+
c.bench_function("bzero_bset_block_memset", |b| {
54+
let start = allocate_aligned(BLOCK_META_BYTES);
55+
let end = start + BLOCK_META_BYTES;
56+
57+
b.iter(|| unsafe {
58+
libc::memset(start.as_mut_ref() as *mut c_void, 0xff, end - start);
59+
libc::memset(start.as_mut_ref() as *mut c_void, 0x00, end - start);
60+
})
61+
});
62+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod bzero_bset;
2+
3+
pub use criterion::Criterion;
4+
5+
pub fn bench(c: &mut Criterion) {
6+
bzero_bset::bench(c);
7+
}

benches/regular_bench/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub use criterion::Criterion;
2+
3+
mod bulk_meta;
4+
5+
pub fn bench(c: &mut Criterion) {
6+
bulk_meta::bench(c);
7+
}

0 commit comments

Comments
 (0)