Use Revision and Durability directly in input Value (#902)

`Stamp` contains a lot of padding bytes, wasting 7 bytes per field for inputs due to the array packing.
By packing each field into a separate array we regain that space
This commit is contained in:
Lukas Wirth 2025-06-04 16:26:49 +02:00 committed by GitHub
parent 2c57c5628b
commit 38a3c9e06d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 43 additions and 46 deletions

View file

@ -80,14 +80,12 @@ macro_rules! setup_input_struct {
const FIELD_DEBUG_NAMES: &'static [&'static str] = &[$(stringify!($field_id)),*];
type Singleton = $zalsa::macro_if! {if $is_singleton {$zalsa::input::Singleton} else {$zalsa::input::NotSingleton}};
/// The input struct (which wraps an `Id`)
type Struct = $Struct;
/// A (possibly empty) tuple of the fields for this struct.
type Fields = ($($field_ty,)*);
/// A array of [`StampedValue<()>`](`StampedValue`) tuples, one per each of the value fields.
type Stamps = [$zalsa::Stamp; $N];
type Revisions = [$zalsa::Revision; $N];
type Durabilities = [$zalsa::Durability; $N];
}
impl $Configuration {
@ -274,8 +272,8 @@ macro_rules! setup_input_struct {
let zalsa = db.zalsa();
let current_revision = zalsa.current_revision();
let ingredient = $Configuration::ingredient_(zalsa);
let (fields, stamps) = builder::builder_into_inner(self, current_revision);
ingredient.new_input(db.as_dyn_database(), fields, stamps)
let (fields, revision, durabilities) = builder::builder_into_inner(self, current_revision);
ingredient.new_input(db.as_dyn_database(), fields, revision, durabilities)
}
}
@ -294,10 +292,8 @@ macro_rules! setup_input_struct {
}
}
pub(super) fn builder_into_inner(builder: $Builder, revision: $zalsa::Revision) -> (($($field_ty,)*), [$zalsa::Stamp; $N]) {
let stamps = [$($zalsa::stamp(revision, builder.durabilities[$field_index])),*];
(builder.fields, stamps)
pub(super) fn builder_into_inner(builder: $Builder, revision: $zalsa::Revision) -> (($($field_ty,)*), [$zalsa::Revision; $N], [$zalsa::Durability; $N]) {
(builder.fields, [revision; $N], [$(builder.durabilities[$field_index]),*])
}
#[must_use]

View file

@ -141,7 +141,6 @@ impl ActiveQuery {
pub(super) fn stamp(&self) -> Stamp {
Stamp {
value: (),
durability: self.durability,
changed_at: self.changed_at,
}

View file

@ -14,7 +14,7 @@ use crate::id::{AsId, FromId, FromIdWithDb};
use crate::ingredient::Ingredient;
use crate::input::singleton::{Singleton, SingletonChoice};
use crate::key::DatabaseKeyIndex;
use crate::plumbing::{Jar, Stamp};
use crate::plumbing::Jar;
use crate::sync::Arc;
use crate::table::memo::{MemoTable, MemoTableTypes};
use crate::table::{Slot, Table};
@ -35,8 +35,11 @@ pub trait Configuration: Any {
/// A (possibly empty) tuple of the fields for this struct.
type Fields: Send + Sync;
/// A array of [`StampedValue<()>`](`StampedValue`) tuples, one per each of the value fields.
type Stamps: Send + Sync + fmt::Debug + IndexMut<usize, Output = Stamp>;
/// A array of [`Revision`], one per each of the value fields.
type Revisions: Send + Sync + fmt::Debug + IndexMut<usize, Output = Revision>;
/// A array of [`Durability`], one per each of the value fields.
type Durabilities: Send + Sync + fmt::Debug + IndexMut<usize, Output = Durability>;
}
pub struct JarImpl<C: Configuration> {
@ -100,13 +103,20 @@ impl<C: Configuration> IngredientImpl<C> {
DatabaseKeyIndex::new(self.ingredient_index, id.as_id())
}
pub fn new_input(&self, db: &dyn Database, fields: C::Fields, stamps: C::Stamps) -> C::Struct {
pub fn new_input(
&self,
db: &dyn Database,
fields: C::Fields,
revisions: C::Revisions,
durabilities: C::Durabilities,
) -> C::Struct {
let (zalsa, zalsa_local) = db.zalsas();
let id = self.singleton.with_scope(|| {
zalsa_local.allocate(zalsa, self.ingredient_index, |_| Value::<C> {
fields,
stamps,
revisions,
durabilities,
memos: Default::default(),
})
});
@ -139,14 +149,14 @@ impl<C: Configuration> IngredientImpl<C> {
// Also, we don't access any other data from the table while `r` is active.
let data = unsafe { &mut *data_raw };
let stamp = &mut data.stamps[field_index];
data.revisions[field_index] = runtime.current_revision();
if stamp.durability != Durability::MIN {
runtime.report_tracked_write(stamp.durability);
let field_durability = &mut data.durabilities[field_index];
if *field_durability != Durability::MIN {
runtime.report_tracked_write(*field_durability);
}
*field_durability = durability.unwrap_or(*field_durability);
stamp.durability = durability.unwrap_or(stamp.durability);
stamp.changed_at = runtime.current_revision();
setter(&mut data.fields)
}
@ -174,11 +184,12 @@ impl<C: Configuration> IngredientImpl<C> {
let field_ingredient_index = self.ingredient_index.successor(field_index);
let id = id.as_id();
let value = Self::data(zalsa, id);
let stamp = &value.stamps[field_index];
let durability = value.durabilities[field_index];
let revision = value.revisions[field_index];
zalsa_local.report_tracked_read_simple(
DatabaseKeyIndex::new(field_ingredient_index, id),
stamp.durability,
stamp.changed_at,
durability,
revision,
);
&value.fields
}
@ -251,8 +262,11 @@ where
/// a particular revision.
fields: C::Fields,
/// The revision and durability information for each field: when did this field last change.
stamps: C::Stamps,
/// Revisions of the fields.
revisions: C::Revisions,
/// Durabilities of the fields.
durabilities: C::Durabilities,
/// Memos
memos: MemoTable,

View file

@ -59,7 +59,7 @@ where
) -> VerifyResult {
let zalsa = db.zalsa();
let value = <IngredientImpl<C>>::data(zalsa, input);
VerifyResult::changed_if(value.stamps[self.field_index].changed_at > revision)
VerifyResult::changed_if(value.revisions[self.field_index] > revision)
}
fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {

View file

@ -84,6 +84,7 @@ pub mod plumbing {
pub use crate::attach::{attach, with_attached_database};
pub use crate::cycle::{CycleRecoveryAction, CycleRecoveryStrategy};
pub use crate::database::{current_revision, Database};
pub use crate::durability::Durability;
pub use crate::id::{AsId, FromId, FromIdWithDb, Id};
pub use crate::ingredient::{Ingredient, Jar, Location};
pub use crate::key::DatabaseKeyIndex;
@ -92,7 +93,7 @@ pub mod plumbing {
NewMemoIngredientIndices,
};
pub use crate::revision::Revision;
pub use crate::runtime::{stamp, Runtime, Stamp, StampedValue};
pub use crate::runtime::{stamp, Runtime, Stamp};
pub use crate::salsa_struct::SalsaStructInDb;
pub use crate::storage::{HasStorage, Storage};
pub use crate::tracked_struct::TrackedStructInDb;

View file

@ -116,31 +116,18 @@ impl std::fmt::Debug for Running<'_> {
}
#[derive(Copy, Clone, Debug)]
pub struct StampedValue<V> {
pub value: V,
pub struct Stamp {
pub durability: Durability,
pub changed_at: Revision,
}
pub type Stamp = StampedValue<()>;
pub fn stamp(revision: Revision, durability: Durability) -> Stamp {
StampedValue {
value: (),
Stamp {
durability,
changed_at: revision,
}
}
impl<V> StampedValue<V> {
// FIXME: Use or remove this.
#[allow(dead_code)]
pub(crate) fn merge_revision_info<U>(&mut self, other: &StampedValue<U>) {
self.durability = self.durability.min(other.durability);
self.changed_at = self.changed_at.max(other.changed_at);
}
}
impl Default for Runtime {
fn default() -> Self {
Runtime {

View file

@ -17,7 +17,7 @@ use crate::ingredient::{Ingredient, Jar};
use crate::key::DatabaseKeyIndex;
use crate::plumbing::ZalsaLocal;
use crate::revision::OptionalAtomicRevision;
use crate::runtime::StampedValue;
use crate::runtime::Stamp;
use crate::salsa_struct::SalsaStructInDb;
use crate::sync::Arc;
use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut};
@ -435,7 +435,7 @@ where
zalsa: &'db Zalsa,
zalsa_local: &'db ZalsaLocal,
current_revision: Revision,
current_deps: &StampedValue<()>,
current_deps: &Stamp,
fields: C::Fields<'db>,
) -> Id {
let value = |_| Value {
@ -496,7 +496,7 @@ where
zalsa: &'db Zalsa,
current_revision: Revision,
mut id: Id,
current_deps: &StampedValue<()>,
current_deps: &Stamp,
fields: C::Fields<'db>,
) -> Result<Id, C::Fields<'db>> {
let data_raw = Self::data_raw(zalsa.table(), id);