-
Notifications
You must be signed in to change notification settings - Fork 314
[Design]: Changes to improve performance #291
Conversation
- Track disposables in a list with lock instead of a concurrent bag - Allocate a single _createServiceAccessor instead of one per instance - Removed syncObject and just locked the _resolvedServices
} | ||
} | ||
|
||
private object CaptureDisposable(object service) | ||
{ | ||
// Skip capturing disposables for the root | ||
if (_root == this) |
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.
Nobody disposes the root scope, an argument can be made that it is possible but the interface for the top level (IServiceProvider) requires a downcast to IDisposable to do so and that's pretty rare unless you're scoped. We can let the GC handle those objects.
return value; | ||
} | ||
|
||
value = valueFactory(key, arg); |
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.
You can make this code a little clearer by getting rid of the while
and changing the TryAdd
to GetOrAdd(key, value)
. If your add fails, it's because someone was contending with you to also add a value and so you'll get the value they added.
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.
👍
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.
Feels like this is easier to read.
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.
Follow your ❤️ - but this gives me the impression that it will run an arbitrary number of times, but it will only ever run 1.5 times at most.
overall looks good - main point here seems to be assuming that the service provider is low-contention and we can do simpler/lighter things because of that. |
// This lets us pass a state parameter allocation free GetOrAdd | ||
internal static TValue GetOrAdd<TKey, TValue, TArg>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TArg, TValue> valueFactory, TArg arg) | ||
{ | ||
if (dictionary == null) |
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.
If it's internal code, consider removing these exceptions and replacing it with Debug.Assert
instead.
788k -> 384k (-51%) If you didn't want to go full pooling, but were ok with partial pooling, you could pool the dictionary and list and clear them up in the disposable; which would trim it to 144k but limit the scope for the pooling. 788k -> 144k (-82%) Though likely with that you'd need to return to an object for locking and add bool for dispose check |
updated. Going to try a few more things:
|
- Only use it for transient objects - Use _resolvedServices as a lockObject to avoid allocating a lockObj
@benaadams New changes for ya. |
Looks |
Merged |
Measurements on the way...
/cc @rynowak @halter73 @pranavkm @lodejard @benaadams