mirror of
https://github.com/salsa-rs/salsa.git
synced 2024-12-25 05:29:43 +00:00
Merge pull request #138 from matklad/flexible-gc
Make GC API more orthogonal and flexible
This commit is contained in:
commit
1002d7e70a
6 changed files with 110 additions and 39 deletions
|
@ -10,7 +10,7 @@ use crate::runtime::Revision;
|
||||||
use crate::runtime::Runtime;
|
use crate::runtime::Runtime;
|
||||||
use crate::runtime::RuntimeId;
|
use crate::runtime::RuntimeId;
|
||||||
use crate::runtime::StampedValue;
|
use crate::runtime::StampedValue;
|
||||||
use crate::{Database, Event, EventKind, SweepStrategy};
|
use crate::{Database, DiscardIf, DiscardWhat, Event, EventKind, SweepStrategy};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -952,7 +952,8 @@ where
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, keep only if it was used in this revision.
|
// Otherwise, drop only value or the whole memo accoring to the
|
||||||
|
// strategy.
|
||||||
QueryState::Memoized(memo) => {
|
QueryState::Memoized(memo) => {
|
||||||
debug!(
|
debug!(
|
||||||
"sweep({:?}({:?})): last verified at {:?}, current revision {:?}",
|
"sweep({:?}({:?})): last verified at {:?}, current revision {:?}",
|
||||||
|
@ -970,12 +971,18 @@ where
|
||||||
// revision, since we are holding the write lock
|
// revision, since we are holding the write lock
|
||||||
// when we read `revision_now`.
|
// when we read `revision_now`.
|
||||||
assert!(memo.verified_at <= revision_now);
|
assert!(memo.verified_at <= revision_now);
|
||||||
|
match strategy.discard_if {
|
||||||
if !strategy.keep_values {
|
DiscardIf::Never => true,
|
||||||
memo.value = None;
|
DiscardIf::Outdated if memo.verified_at == revision_now => true,
|
||||||
|
DiscardIf::Outdated | DiscardIf::Always => match strategy.discard_what {
|
||||||
|
DiscardWhat::Nothing => true,
|
||||||
|
DiscardWhat::Values => {
|
||||||
|
memo.value = None;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
DiscardWhat::Everything => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
memo.verified_at == revision_now
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
98
src/lib.rs
98
src/lib.rs
|
@ -204,30 +204,90 @@ impl<DB: Database> fmt::Debug for EventKind<DB> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The sweep strategy controls what data we will keep/discard when we
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
/// do a GC-sweep. The default (`SweepStrategy::default`) is to keep
|
enum DiscardIf {
|
||||||
/// all memoized values used in the current revision.
|
Never,
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
Outdated,
|
||||||
pub struct SweepStrategy {
|
Always,
|
||||||
keep_values: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SweepStrategy {
|
impl Default for DiscardIf {
|
||||||
/// Causes us to discard memoized *values* but keep the
|
fn default() -> DiscardIf {
|
||||||
/// *dependencies*. This means you will have to recompute the
|
DiscardIf::Never
|
||||||
/// results from any queries you execute but does permit you to
|
|
||||||
/// quickly determine if a value is still up to date.
|
|
||||||
pub fn discard_values(self) -> SweepStrategy {
|
|
||||||
SweepStrategy {
|
|
||||||
keep_values: false,
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SweepStrategy {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
fn default() -> Self {
|
enum DiscardWhat {
|
||||||
SweepStrategy { keep_values: true }
|
Nothing,
|
||||||
|
Values,
|
||||||
|
Everything,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiscardWhat {
|
||||||
|
fn default() -> DiscardWhat {
|
||||||
|
DiscardWhat::Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sweep strategy controls what data we will keep/discard when we
|
||||||
|
/// do a GC-sweep. The default (`SweepStrategy::default`) is a no-op,
|
||||||
|
/// use `SweepStrategy::discard_outdated` constructor or `discard_*`
|
||||||
|
/// and `sweep_*` builder functions to construct useful strategies.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub struct SweepStrategy {
|
||||||
|
discard_if: DiscardIf,
|
||||||
|
discard_what: DiscardWhat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SweepStrategy {
|
||||||
|
/// Convenience function that discards all data not used thus far in the
|
||||||
|
/// current revision.
|
||||||
|
///
|
||||||
|
/// Equivalent to `SweepStrategy::default().discard_everything()`.
|
||||||
|
pub fn discard_outdated() -> SweepStrategy {
|
||||||
|
SweepStrategy::default()
|
||||||
|
.discard_everything()
|
||||||
|
.sweep_outdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects query values.
|
||||||
|
///
|
||||||
|
/// Query dependencies are left in the database, which allows to quickly
|
||||||
|
/// determine if the query is up to date, and avoid recomputing
|
||||||
|
/// dependencies.
|
||||||
|
pub fn discard_values(self) -> SweepStrategy {
|
||||||
|
SweepStrategy {
|
||||||
|
discard_what: self.discard_what.max(DiscardWhat::Values),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects both values and information about dependencies.
|
||||||
|
///
|
||||||
|
/// Dependant queries will be recomputed even if all inputs to this query
|
||||||
|
/// stay the same.
|
||||||
|
pub fn discard_everything(self) -> SweepStrategy {
|
||||||
|
SweepStrategy {
|
||||||
|
discard_what: self.discard_what.max(DiscardWhat::Everything),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process all keys, not verefied at the current revision.
|
||||||
|
pub fn sweep_outdated(self) -> SweepStrategy {
|
||||||
|
SweepStrategy {
|
||||||
|
discard_if: self.discard_if.max(DiscardIf::Outdated),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process all keys.
|
||||||
|
pub fn sweep_all_revisions(self) -> SweepStrategy {
|
||||||
|
SweepStrategy {
|
||||||
|
discard_if: self.discard_if.max(DiscardIf::Always),
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ fn compute_one() {
|
||||||
|
|
||||||
// Memoized, but will compute fibonacci(5) again
|
// Memoized, but will compute fibonacci(5) again
|
||||||
db.compute(5);
|
db.compute(5);
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
db,
|
db,
|
||||||
|
@ -64,7 +64,7 @@ fn compute_switch() {
|
||||||
MaxQuery => (),
|
MaxQuery => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
// Now we just have `Triangular` and not `Fibonacci`
|
// Now we just have `Triangular` and not `Fibonacci`
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
|
@ -80,7 +80,7 @@ fn compute_switch() {
|
||||||
// Now run `compute` *again* in next revision.
|
// Now run `compute` *again* in next revision.
|
||||||
db.salsa_runtime().next_revision();
|
db.salsa_runtime().next_revision();
|
||||||
assert_eq!(db.compute(5), 15);
|
assert_eq!(db.compute(5), 15);
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
// We keep triangular, but just the outermost one.
|
// We keep triangular, but just the outermost one.
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
|
@ -109,7 +109,7 @@ fn compute_all() {
|
||||||
db.compute_all();
|
db.compute_all();
|
||||||
db.salsa_runtime().next_revision();
|
db.salsa_runtime().next_revision();
|
||||||
db.compute_all();
|
db.compute_all();
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
db,
|
db,
|
||||||
|
@ -137,7 +137,7 @@ fn compute_all() {
|
||||||
MaxQuery => (()),
|
MaxQuery => (()),
|
||||||
}
|
}
|
||||||
|
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
// We no longer used `Compute(5)` and `Triangular(5)`; note that
|
// We no longer used `Compute(5)` and `Triangular(5)`; note that
|
||||||
// `UseTriangularQuery(5)` is not collected, as it is an input.
|
// `UseTriangularQuery(5)` is not collected, as it is an input.
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn sweep_default() {
|
||||||
|
|
||||||
// fibonacci is a constant, so it will not be invalidated,
|
// fibonacci is a constant, so it will not be invalidated,
|
||||||
// hence we keep 3 and 5 but remove the rest.
|
// hence we keep 3 and 5 but remove the rest.
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
db,
|
db,
|
||||||
FibonacciQuery => (3, 5),
|
FibonacciQuery => (3, 5),
|
||||||
|
@ -31,8 +31,12 @@ fn sweep_default() {
|
||||||
db.assert_log(&[]);
|
db.assert_log(&[]);
|
||||||
|
|
||||||
// Same but we discard values this time.
|
// Same but we discard values this time.
|
||||||
db.sweep_all(SweepStrategy::default().discard_values());
|
db.sweep_all(
|
||||||
db.sweep_all(SweepStrategy::default());
|
SweepStrategy::default()
|
||||||
|
.discard_values()
|
||||||
|
.sweep_all_revisions(),
|
||||||
|
);
|
||||||
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
db,
|
db,
|
||||||
FibonacciQuery => (3, 5),
|
FibonacciQuery => (3, 5),
|
||||||
|
|
|
@ -18,7 +18,7 @@ fn one_rev() {
|
||||||
|
|
||||||
// Everything was used in this revision, so
|
// Everything was used in this revision, so
|
||||||
// nothing gets collected.
|
// nothing gets collected.
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
assert_eq!(k.len(), 6);
|
assert_eq!(k.len(), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ fn two_rev_nothing() {
|
||||||
|
|
||||||
// Nothing was used in this revision, so
|
// Nothing was used in this revision, so
|
||||||
// everything gets collected.
|
// everything gets collected.
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
let k: Vec<_> = db.query(FibonacciQuery).entries();
|
let k: Vec<_> = db.query(FibonacciQuery).entries();
|
||||||
assert_eq!(k.len(), 0);
|
assert_eq!(k.len(), 0);
|
||||||
|
@ -56,7 +56,7 @@ fn two_rev_one_use() {
|
||||||
|
|
||||||
// fibonacci is a constant, so it will not be invalidated,
|
// fibonacci is a constant, so it will not be invalidated,
|
||||||
// hence we keep `fibonacci(5)` but remove 0..=4.
|
// hence we keep `fibonacci(5)` but remove 0..=4.
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
db,
|
db,
|
||||||
|
@ -80,7 +80,7 @@ fn two_rev_two_uses() {
|
||||||
|
|
||||||
// fibonacci is a constant, so it will not be invalidated,
|
// fibonacci is a constant, so it will not be invalidated,
|
||||||
// hence we keep 3 and 5 but remove the rest.
|
// hence we keep 3 and 5 but remove the rest.
|
||||||
db.sweep_all(SweepStrategy::default());
|
db.sweep_all(SweepStrategy::discard_outdated());
|
||||||
|
|
||||||
assert_keys! {
|
assert_keys! {
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -114,7 +114,7 @@ impl rand::distributions::Distribution<ReadOp> for rand::distributions::Standard
|
||||||
let key = rng.gen::<usize>() % 10;
|
let key = rng.gen::<usize>() % 10;
|
||||||
return ReadOp::Get(query, key);
|
return ReadOp::Get(query, key);
|
||||||
}
|
}
|
||||||
let mut strategy = SweepStrategy::default();
|
let mut strategy = SweepStrategy::discard_outdated();
|
||||||
if rng.gen_bool(0.5) {
|
if rng.gen_bool(0.5) {
|
||||||
strategy = strategy.discard_values();
|
strategy = strategy.discard_values();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue