Skip to content

Commit

Permalink
update 18d12e7a8612bbbb04894ecb6f14f2b550ac883e
Browse files Browse the repository at this point in the history
  • Loading branch information
tomtomjhj committed Sep 17, 2023
1 parent 64f7e03 commit 3a93b5d
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 62 deletions.
19 changes: 3 additions & 16 deletions homework/doc/hash_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This homework is in 2 parts:
2. (40 points) Performance:
After you learn about relaxed memory semantics,
optimize the implementation by relaxing the ordering on the atomic accesses.
We recommend working on this part after finishing the [Arc homework](./arc.md).

## Part 1: Split-ordered list in sequentially consistent memory model
1. Fully understand the following reading materials.
Expand Down Expand Up @@ -37,22 +38,8 @@ Use release-acquire synchronization for atomic accesses, just like many other da


## Testing
Tests in `tests/{growable_array,hash_table}.rs` use the map test functions defined in `src/test/adt/map.rs`.
* `smoke`:
Simple test case that tries a few operations. Useful for debugging.
* `stress_sequential`:
Runs many operations in a single thread and tests if it works like a map data structure using `std::collections::HashMap` as reference.
* `lookup_concurrent`:
Inserts keys sequentially, then concurrently run lookup in multiple threads.
* `insert_concurrent`:
Inserts concurrently.
* `stress_concurrent`:
Randomly runs many operations concurrently.
* `log_concurrent`:
Randomly runs many operations concurrently and logs the operations & results per thread.
Then checks the consistency of the log.
For example, if the key `k` was successfully deleted twice, then `k` must have been inserted at least twice.
Unlike `stress_sequential`, this test doesn't guarantee complete correctness.
Tests are defined in `tests/{growable_array,hash_table}.rs`.
They use the common map test functions defined in `src/test/adt/map.rs`.

## Grading (180 points)
Run `./scripts/grade-hash_table.sh`.
Expand Down
1 change: 1 addition & 0 deletions homework/doc/hazard_pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This homework is in 2 parts:
2. (30 points) Performance:
After you learn about relaxed memory semantics,
optimize the implementation by relaxing the ordering.
We recommend working on this part after finishing the [Arc homework](./arc.md).


## Part 1: Hazard pointers in the sequentially consistent memory model
Expand Down
33 changes: 24 additions & 9 deletions homework/doc/list_set.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,28 @@ You will implement two variants.
they do not block other operations.
However, you need to take more care to get it correct.
* You need to validate read operations and handle the failure.
* Since reads and writes can run concurrently, you should use atomic operations to avoid data race.
Specifically, you will use `crossbeam_epoch`'s `Atomic<T>` type.
* Do not use `ReadGuard::restart()`.
Using this correctly requires some extra synchronization
(to be covered in lock-free list lecture),
which makes `SeqLock` somewhat pointless.
The tests assume that `ReadGuard::restart()` is not used.
* Since each node can be read and modified to concurrently,
you should use atomic operations to avoid data race.
Specifically, you will use `crossbeam_epoch`'s `Atomic<T>` type
(instead of `std::sync::AtomicPtr<T>`, due to the next issue).
For `Ordering`, use `SeqCst` everywhere.
In the later part of this course, you will learn that `Relaxed` is sufficient.
But don't use `Relaxed` in this homework, because that would break `cargo_tsan`.
(In the later part of this course, you will learn that `Relaxed` is sufficient.
But don't use `Relaxed` in this homework, because that would break `cargo_tsan`.)
* Since a node can be removed while another thread is reading,
reclamation of the node should be deferred.
Again, you will need to use `crossbeam_epoch`.
You can handle this semi-automatically with `crossbeam_epoch`.

Fill in the `todo!()`s in `list_set/{fine_grained,optimistic_fine_grained}.rs` (about 40 + 80 lines of code).
As in the [Linked List homework](./linked_list.md), you will need to use some unsafe operations.

## Testing
Tests are defined in `tests/list_set/{fine_grained,optimistic_fine_grained}.rs`.
Some of them use the common set test functions defined in `src/test/adt/set.rs`.

## Grading (100 points)
Run
Expand All @@ -39,15 +49,20 @@ Run
```

For each module `fine_grained` and `optimistic_fine_grained`,
the grader runs the following tests with `cargo`, `cargo_asan`, and `cargo_tsan` in the following order.
the grader runs the tests
with `cargo`, `cargo_asan`, and `cargo_tsan` in the following order.
1. `stress_sequential` (5 points)
1. `stress_concurrent` (10 points)
1. `log_concurrent` (15 points)
1. `iter_consistent` (15 points)

For `optimistic_fine_grained`, the grade additionally runs
1. `read_no_block` (5 points)
1. `iter_invalidate_end` (5 points)
For the above tests, if a test fails in a module, then the later tests in the same module will not be run.

For `optimistic_fine_grained`, the grader additionally runs the following tests
(10 points if all of them passes, otherwise 0).
* `read_no_block`
* `iter_invalidate_end`
* `iter_invalidate_deleted`

## Submission
```sh
Expand Down
2 changes: 1 addition & 1 deletion homework/scripts/grade-hash_table.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ split_ordered_list_fail=${#TEST_NAMES[@]}

echo "1. Running tests..."
for r in "${!RUNNERS[@]}"; do
RUNNER=${RUNNERS[r]}
for t in "${!TEST_NAMES[@]}"; do
TEST_NAME=${TEST_NAMES[t]}
RUNNER=${RUNNERS[r]}
TIMEOUT=${TIMEOUTS[ t * ${#RUNNERS[@]} + r ]}
# run only if no test has failed yet
if [ $t -lt $growable_array_fail ]; then
Expand Down
35 changes: 17 additions & 18 deletions homework/scripts/grade-list_set.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ TIMEOUT=1m
export RUST_TEST_THREADS=1


# 1. Common tests (45 + 45)
echo "1. Running common tests"
REPS=3
COMMON_TESTS=(
"stress_sequential"
Expand All @@ -31,11 +29,12 @@ RUNNERS=(
# the index of the last failed test
fine_grained_fail=${#COMMON_TESTS[@]}
optimistic_fine_grained_fail=${#COMMON_TESTS[@]}
others_failed=false

for r in "${!RUNNERS[@]}"; do
RUNNER=${RUNNERS[r]}
for t in "${!COMMON_TESTS[@]}"; do
TEST_NAME=${COMMON_TESTS[t]}
RUNNER=${RUNNERS[r]}
# run only if no test has failed yet
if [ $t -lt $fine_grained_fail ]; then
echo "Testing fine_grained $TEST_NAME with $RUNNER, timeout $TIMEOUT..."
Expand All @@ -58,24 +57,24 @@ for r in "${!RUNNERS[@]}"; do
done
fi
done

if [ "$others_failed" == false ]; then
echo "Running additional tests for optimistic_fine_grained with $RUNNER, timeout $TIMEOUT..."
TESTS=(
"--test list_set -- --exact optimistic_fine_grained::read_no_block"
"--test list_set -- --exact optimistic_fine_grained::iter_invalidate_end"
"--test list_set -- --exact optimistic_fine_grained::iter_invalidate_deleted"
)
if [ $(run_tests) -ne 0 ]; then
others_failed=true
fi
fi
done

SCORES=( 0 5 15 30 45 )
SCORE=$(( SCORES[fine_grained_fail] + SCORES[optimistic_fine_grained_fail] ))

# 2. other tests (5 + 5)
echo "2. Running other tests for optimistic_fine_grained"
RUNNER="cargo"
OTHER_TESTS=(
"--test list_set -- --exact optimistic_fine_grained::read_no_block"
"--test list_set -- --exact optimistic_fine_grained::iter_invalidate_end"
)
for TEST in "${OTHER_TESTS[@]}"; do
echo "Running with $RUNNER..."
TESTS=("$TEST")
if [ $(run_tests) -eq 0 ]; then
SCORE=$(( SCORE + 5 ))
fi
done
if [ "$others_failed" == false ]; then
SCORE=$(( SCORE + 10 ))
fi

echo "Score: $SCORE / 100"
13 changes: 12 additions & 1 deletion homework/src/test/adt/map.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Testing utilities for map types
use core::fmt::Debug;
use core::hash::Hash;
use core::marker::PhantomData;
Expand All @@ -10,7 +12,7 @@ use rand::prelude::*;

use crossbeam_epoch::pin;

pub fn stress_sequential<
fn stress_sequential<
K: Debug + Clone + Eq + Hash + RandGen,
M: Default + SequentialMap<K, usize>,
>(
Expand Down Expand Up @@ -74,6 +76,7 @@ pub fn stress_sequential<
}
}

/// Provides `SequentialMap` interface for `ConcurrentMap`.
#[derive(Debug)]
pub struct Sequentialize<K: ?Sized, V, M: ConcurrentMap<K, V>> {
inner: M,
Expand Down Expand Up @@ -104,6 +107,8 @@ impl<K: ?Sized, V, M: ConcurrentMap<K, V>> SequentialMap<K, V> for Sequentialize
}
}

/// Runs many operations in a single thread and tests if it works like a set data structure using
/// `std::collections::HashMap` as reference.
pub fn stress_concurrent_sequential<
K: Debug + Clone + Eq + Hash + RandGen,
M: Default + ConcurrentMap<K, usize>,
Expand All @@ -113,6 +118,7 @@ pub fn stress_concurrent_sequential<
stress_sequential::<K, Sequentialize<K, usize, M>>(steps);
}

/// Runs random lookup operations concurrently.
pub fn lookup_concurrent<
K: Debug + Eq + Hash + RandGen + Send + Sync,
M: Default + Sync + ConcurrentMap<K, usize>,
Expand Down Expand Up @@ -169,6 +175,7 @@ pub fn lookup_concurrent<
});
}

/// Runs random insert operations concurrently.
pub fn insert_concurrent<
K: Debug + Eq + Hash + RandGen,
M: Default + Sync + ConcurrentMap<K, usize>,
Expand Down Expand Up @@ -218,6 +225,7 @@ impl<K, V> Log<K, V> {
}
}

/// Randomly runs many operations concurrently.
pub fn stress_concurrent<
K: Debug + Eq + Hash + RandGen,
M: Default + Sync + ConcurrentMap<K, usize>,
Expand Down Expand Up @@ -302,6 +310,9 @@ fn assert_logs_consistent<K: Clone + Eq + Hash, V: Clone + Eq + Hash>(logs: &Vec
}
}

/// Randomly runs many operations concurrently and logs the operations & results per thread. Then
/// checks the consistency of the log. For example, if the key `k` was successfully deleted twice,
/// then `k` must have been inserted at least twice.
pub fn log_concurrent<
K: Debug + Clone + Eq + Hash + Send + RandGen,
M: Default + Sync + ConcurrentMap<K, usize>,
Expand Down
2 changes: 2 additions & 0 deletions homework/src/test/adt/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
//! Testing utilities for abstract data types
pub mod map;
pub mod set;
8 changes: 8 additions & 0 deletions homework/src/test/adt/set.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Testing utilities for set types
use core::fmt::Debug;
use core::hash::Hash;
use rand::prelude::*;
Expand All @@ -7,6 +9,8 @@ use std::thread;
use crate::test::RandGen;
use crate::ConcurrentSet;

/// Runs many operations in a single thread and tests if it works like a set data structure using
/// `std::collections::HashSet` as reference.
pub fn stress_sequential<K: Debug + Clone + Eq + Hash + RandGen, S: Default + ConcurrentSet<K>>(
steps: usize,
) {
Expand Down Expand Up @@ -90,6 +94,7 @@ impl<K> Log<K> {
}
}

/// Randomly runs many operations concurrently.
pub fn stress_concurrent<K: Debug + Clone + Eq + RandGen, S: Default + Sync + ConcurrentSet<K>>(
threads: usize,
steps: usize,
Expand Down Expand Up @@ -160,6 +165,9 @@ fn assert_logs_consistent<K: Clone + Eq + Hash>(logs: &Vec<Vec<Log<K>>>) {
}
}

/// Randomly runs many operations concurrently and logs the operations & results per thread. Then
/// checks the consistency of the log. For example, if the key `k` was successfully deleted twice,
/// then `k` must have been inserted at least twice.
pub fn log_concurrent<
K: Debug + Clone + Eq + Hash + Send + RandGen,
S: Default + Sync + ConcurrentSet<K>,
Expand Down
2 changes: 2 additions & 0 deletions homework/src/test/mock.rs → homework/src/test/loom.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Re-exports loom if `feature = "check-loom"`. Otherwise, std.
#[cfg(not(feature = "check-loom"))]
pub use std::*;

Expand Down
5 changes: 2 additions & 3 deletions homework/src/test/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Utilities for testing
//! <https://stackoverflow.com/a/44541071>
#![doc(hidden)]
// <https://stackoverflow.com/a/44541071>

pub mod adt;
pub mod mock;
pub mod loom;
pub mod rand;

pub use rand::RandGen;
2 changes: 2 additions & 0 deletions homework/src/test/rand.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Utilities for random value generator
use rand::{distributions::Alphanumeric, rngs::ThreadRng, Rng};

/// Types that has random generator
Expand Down
14 changes: 7 additions & 7 deletions homework/tests/arc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cs431_homework::test::mock::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use cs431_homework::test::loom::sync::atomic::{AtomicUsize, Ordering::Relaxed};

/// Used for testing if `T` of `Arc<T>` is dropped exactly once.
struct Canary(*const AtomicUsize);
Expand All @@ -17,9 +17,9 @@ mod basic {
use cs431_homework::Arc;

use super::Canary;
use cs431_homework::test::mock::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use cs431_homework::test::mock::sync::mpsc::channel;
use cs431_homework::test::mock::thread;
use cs431_homework::test::loom::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use cs431_homework::test::loom::sync::mpsc::channel;
use cs431_homework::test::loom::thread;

#[test]
fn manually_share_arc() {
Expand Down Expand Up @@ -144,9 +144,9 @@ mod basic {

mod correctness {
use super::Canary;
use cs431_homework::test::mock::model;
use cs431_homework::test::mock::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use cs431_homework::test::mock::thread;
use cs431_homework::test::loom::model;
use cs431_homework::test::loom::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use cs431_homework::test::loom::thread;
use cs431_homework::Arc;

#[test]
Expand Down
8 changes: 4 additions & 4 deletions homework/tests/hazard_pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ fn stack_queue() {
mod sync {
use core::ptr;
use cs431_homework::hazard_pointer::*;
use cs431_homework::test::mock::model;
use cs431_homework::test::mock::sync::atomic::{AtomicPtr, AtomicUsize, Ordering::*};
use cs431_homework::test::mock::sync::Arc;
use cs431_homework::test::mock::thread;
use cs431_homework::test::loom::model;
use cs431_homework::test::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering::*};
use cs431_homework::test::loom::sync::Arc;
use cs431_homework::test::loom::thread;

#[test]
fn try_protect_collect_sync() {
Expand Down
2 changes: 1 addition & 1 deletion homework/tests/list_set/fine_grained.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fn log_concurrent() {
set::log_concurrent::<u8, FineGrainedListSet<u8>>(THREADS, STEPS);
}

/// Check the consistency of iterator while other operations are running concurrently.
#[test]
fn iter_consistent() {
const THREADS: usize = 15;
Expand Down Expand Up @@ -73,7 +74,6 @@ fn iter_consistent() {
done.store(true, Release);
});
}
// iterator consistency check
let _unused = s.spawn(|| {
while !done.load(Acquire) {
let snapshot = set.iter().copied().collect::<Vec<_>>();
Expand Down
Loading

0 comments on commit 3a93b5d

Please sign in to comment.