Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tables #564

Merged
merged 27 commits into from
Aug 20, 2024
Merged

Tables #564

merged 27 commits into from
Aug 20, 2024

Conversation

nikomatsakis
Copy link
Member

This PR modifies the way we store salsa struct to use a central table/page system. The results of tracked functions are now stored in memos/syncs attached to a particular slot.

This lays the foundation for serialization/deserialization as well as speculative execution by using copy-on-write pages, but it also avoids a ton of centralized hashing. Tracked functions that take a single salsa struct as argument now avoid hashtable lookups altogether.

In the future I'd like to improve the way that tracked functions with >1 argument work by extending the memo table to optionally store a per-struct hashmap, but that can be a separate PR.

Copy link

netlify bot commented Aug 16, 2024

Deploy Preview for salsa-rs canceled.

Name Link
🔨 Latest commit 02036ff
🔍 Latest deploy log https://app.netlify.com/sites/salsa-rs/deploys/66c4a3bf75d33e00083d33fb

Copy link

codspeed-hq bot commented Aug 16, 2024

CodSpeed Performance Report

Merging #564 will improve performances by ×2.7

Comparing nikomatsakis:tables (02036ff) with master (08820ea)

Summary

⚡ 1 improvements
✅ 7 untouched benchmarks

Benchmarks breakdown

Benchmark master nikomatsakis:tables Change
many_tracked_structs 130.9 µs 48.8 µs ×2.7

@MichaReiser
Copy link
Contributor

Wow, very impressive (I haven't read through the code yet). One understand question:

This lays the foundation for serialization/deserialization as well as speculative execution by using copy-on-write pages, but it also avoids a ton of centralized hashing. Tracked functions that take a single salsa struct as argument now avoid hashtable lookups altogether.

By salsa struct. Does this apply to both inputs and tracked structs or only tracked structs?

@MichaReiser
Copy link
Contributor

It's interesting that some of the other benchmarks regress.


#[allow(type_alias_bounds)]
type ArcMemo<'lt, C: Configuration> = ArcSwap<Memo<<C as Configuration>::Output<'lt>>>;
pub(super) type ArcMemo<'lt, C: Configuration> = Arc<Memo<<C as Configuration>::Output<'lt>>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the remaining use case for ArcMemo? Aren't we now allocating the data in the pages or do the pages only store references to the arc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR, the pages store a reference to the Arc<Memo>. I was debating about that, whether we could replace it with an Id of its own.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, my understanding was that the main change in this PR is that memos are no longer stored in Arcs, instead they're stored directly in Pages to get an arena-like allocation. I remembered that it is important to you that all page-headers have the same layout. Is this the reason why we're keeping an Arc (or Id`) or are there other reasons for not storing the value in the pages? Or am I misunderstanding the change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly. I need to write up some docs on the layout. The big advantage in this PR though is that we do (on the fast path) two array lookups instead of a hash. But the memoized data is still stored in "arcs".

@MichaReiser
Copy link
Contributor

You might already be aware of it: Constant queries no-longer compile:

/// Salsa query to get the builtins scope.
///
/// Can return None if a custom typeshed is used that is missing `builtins.pyi`.
#[salsa::tracked]
pub(crate) fn builtins_scope(db: &dyn Db) -> Option<ScopeId<'_>> {
    let builtins_name =
        ModuleName::new_static("builtins").expect("Expected 'builtins' to be a valid module name");
    let builtins_file = resolve_module(db, builtins_name)?.file();
    Some(global_scope(db, builtins_file))
}
error[E0658]: cannot cast `dyn db::Db` to `dyn Database`, trait upcasting coercion is experimental
  --> crates/red_knot_python_semantic/src/builtins.rs:10:1
   |
10 | #[salsa::tracked]
   | ^^^^^^^^^^^^^^^^^
   |
   = note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
   = note: required when coercing `&'db (dyn db::Db + 'static)` into `&(dyn Database + 'static)`
   = note: this error originates in the macro `salsa::plumbing::setup_tracked_fn` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info)

@nikomatsakis
Copy link
Member Author

By salsa struct. Does this apply to both inputs and tracked structs or only tracked structs?

Salsa struct = interned | input | tracked

@nikomatsakis
Copy link
Member Author

It's interesting that some of the other benchmarks regress.

What regresses?

@nikomatsakis
Copy link
Member Author

You might already be aware of it: Constant queries no-longer compile:

Hmm. Seems like we are missing a test. I did change how constant queries are implemented (it's a bit less efficient now than it was, as it winds up with a silly hashmap; could be fixed, just didn't).

@davidbarsky
Copy link
Contributor

It's interesting that some of the other benchmarks regress.

What regresses?

Here: https://codspeed.io/salsa-rs/salsa/branches/nikomatsakis:tables. For whatever reason, the comment doesn't show that change; you have to click into the report.

  • amortized[Input] (8%, from 3.9µs to 4.2µs)
  • amortized[InternedInput] (8%, from 3.2µs to 3.5µs)
  • mutating[20] (4%, from 19.1µs to 20µs)

@carljm
Copy link
Contributor

carljm commented Aug 16, 2024

It looks like CodSpeed considers those "regressions" to be within the margin of error/noise. I don't know how sophisticated the statistics are that CodSpeed uses to make those decisions, but I'm also not sure that we should read too much into results that CodSpeed has decided are not significant.

@davidbarsky
Copy link
Contributor

davidbarsky commented Aug 16, 2024

Gotcha! The thing I'm unsure of is whether criterion considers these to be noise. Does codespeed delegate to criterion on that front?

@carljm
Copy link
Contributor

carljm commented Aug 16, 2024

Yeah, good q, I don't know...

@MichaReiser
Copy link
Contributor

Gotcha! The thing I'm unsure of is whether criterion considers these to be noise. Does codespeed delegate to criterion on that front?

There's a settings page and it defaults to a 10 margin. It also supports commenting on improvements/regressions only

@nikomatsakis
Copy link
Member Author

My hunch is that this branch is slightly worse on some microbenchmarks (among other things, it is using array lookups instead of pure pointers), but much better in more complex scenarios by avoiding centralized hashing.

@davidbarsky
Copy link
Contributor

That makes sense. A single-digit regression on those benchmarks is feels like it's fine and nothing to be concerned about.

@MichaReiser
Copy link
Contributor

Tracked functions that take a single salsa struct as argument now avoid hashtable lookups altogether.

Is my understanding correct that this is achieved by using the salsa struct ID as ID into the query cache and is based on the assumption that a query is "dense" (likely to be called for all arguments)?

@nikomatsakis
Copy link
Member Author

Is my understanding correct that this is achieved by using the salsa struct ID as ID into the query cache and is based on the assumption that a query is "dense" (likely to be called for all arguments)?

Not exactly. Each tracked function is assigned a MemoIngredientIndex (starting from 0). Each salsa struct has an "id" which corresponds to a slot in one of the tables. The slots then carry an array for memos. This array is grown from 0 to the length of the max memo-ingredient-index with which it is used. The entries are arcs. So I guess you could say that we assume if you invoke any tracked functions on a given struct, you'll invoke many. I'm not sure how this will scale, we could do more sophisticated things.

@nikomatsakis
Copy link
Member Author

Rebased at @davidbarsky's request. Once the CI stuff re-runs I'll merge this, presuming it looks good.

@nikomatsakis
Copy link
Member Author

nikomatsakis commented Aug 20, 2024

@davidbarsky

This is what I see on codspeed:

image

@davidbarsky
Copy link
Contributor

thanks for rebasing! those numbers look good for now; i think this branch is fine to land for now? things can be improved after.

@nikomatsakis nikomatsakis added this pull request to the merge queue Aug 20, 2024
Merged via the queue into salsa-rs:master with commit d5018d5 Aug 20, 2024
10 checks passed
@MichaReiser
Copy link
Contributor

Hmm, I think there's still the issue with const queries not compiling.

@davidbarsky
Copy link
Contributor

Hmm, I think there's still the issue with const queries not compiling.

@MichaReiser made an issue: #565

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants