-
Notifications
You must be signed in to change notification settings - Fork 50
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
Caller Function Cache #235
Caller Function Cache #235
Conversation
…retrieve `Function` objects, this saves allocating lots of `Function` objects in caller contexts.
Co-authored-by: Konstantin Preißer <[email protected]>
@kpreisser I've updated this PR with some extra work, basically adding the same caching for I'd appreciate your re-review :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few notes, but generally this looks good to me, thanks!
However, we will probably need to wait for @peterhuene, to verify e.g. that the usage of only the index
field of the ExternXyz
is correct/sufficient to match the object from the dictionary (I would think so, as the dictionary will only store objects that belong to the current Store
), or if it would technically be required to compare the whole ExternXyz
(store
+ index
).
- Added GC.KeepAlive back in - Using `GetCachedExtern` in `Instance` for functions
I've addressed those three comments.
I would like to add a sanity check that |
So I don't think you'll be able to assert on store ids (there is no way to get the id from the Edit: actually, I don't think that buys us much at all. Wasmtime has a bunch of places it checks for cross-store referencing and will panic, so I'm fairly confident the places where we're assuming these objects come from the same store are safe. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
I'll merge after @kpreisser completes their review.
I don't think checking the If it's not possible to check then I think we're probably good enough as we are. Any objects constructed in the cache will use the store context internally and will presumably detect errors for us. For example
Which I assume will internally sanity check if the store context and the memory are mismatched? |
👍 Additionally, to be on the safe side regarding the private readonly ConcurrentDictionary<(ulong store, nuint index), Function> _funcCache = new();
internal Function GetCachedExtern(ExternFunc @extern)
{
var key = (@extern.store, @extern.index);
if (!_funcCache.TryGetValue(key, out var func))
{
func = new Function(this, @extern);
func = _funcCache.GetOrAdd(key, func);
}
return func;
} This should work as What do you think? |
I've changed the key to a tuple of store/index as @kpreisser suggested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind adding tests that e.g. verify that two consecutive calls of Caller.GetFunction
, Caller.GetMemory
with the same name actually return the same Function
/Memory
instance (so the cache works)?
Apart from that and the comment, this looks good to me, thanks!
A possible optimization idea for the future (especially when a cache is also added for other externs like Table
), would be to check whether the multiple ConcurrentDictionary
s can be combined into a single one, where the key additionally consists of ExternKind
, because AFAIK creating a ConcurrentDictionary
involes a number of allocations, which could then be saved.
… the same object for different functions
…ng something else from the cache
I've added tests for:
I did look into this but it's made a bit difficult by the way I'll probably come back to look at this again soon. It looks like it can be fixed by merging the fields of |
Thanks!
Actually, what I meant was to use something like ConcurrentDictionary<(ExternKind kind, ulong store, nuint index), IExternal> _externCache i.e. add internal Function GetCachedExtern(ExternFunc @extern)
{
var key = (ExternKind.Func, @extern.store, @extern.index);
if (!_externCache.TryGetValue(key, out var func))
{
func = new Function(this, @extern);
func = _externCache.GetOrAdd(key, func);
}
return (Function)func;
}
internal Memory GetCachedExtern(ExternMemory @extern)
{
var key = (ExternKind.Memory, @extern.store, @extern.index);
if (!_externCache.TryGetValue(key, out var mem))
{
mem = new Memory(this, @extern);
mem = _externCache.GetOrAdd(key, mem);
}
return (Memory)mem;
} However, a downside is the additional cast e.g. to |
…at we actually want to check.
That's much simpler than what I had in mind, good idea. I've added that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks again for contributing this!
Added caching to the
Store
forFunction
andMemory
objects. TheCaller
uses this to retrieveFunction
/Memory
objects, this saves allocating lots of objects in caller contexts.This is an alternative to #233 and #224. I prefer this approach to #233 since it gives access to the entire
Function
API, instead of a tiny subset exposed through theCaller
. I prefer it to #224 since theMemory
object remains a class instead of becoming a struct, which is more idiomatic.