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
Remove delegate allocation #2312
Conversation
@@ -471,12 +479,17 @@ public SiloAddress CalculateTargetSilo(GrainId grainId, bool excludeThisSiloIfSt | |||
return excludeThisSiloIfStopping && !Running ? null : MyAddress; | |||
} | |||
|
|||
// excludeMySelf from being a TargetSilo if we're not running and the excludeThisSIloIfStopping flag is true. see the comment in the Stop method. | |||
bool excludeMySelf = !Running && excludeThisSiloIfStopping; | |||
|
|||
// need to implement a binary search, but for now simply traverse the list of silos sorted by their hashes |
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.
The binary search will provide benefit only on relatively large deployments (with silos count > 75). Does it makes sense to implement this?
public LocalGrainDirectory(Silo silo) | ||
{ | ||
log = LogManager.GetLogger("Orleans.GrainDirectory.LocalGrainDirectory"); | ||
|
||
MyAddress = silo.LocalMessageCenter.MyAddress; | ||
isSiloNextInTheRing = (siloAddr, hash, excludeMySelf) => (siloAddr.GetConsistentHashCode() <= hash) && | ||
(!excludeMySelf || !siloAddr.Equals(MyAddress)); |
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.
Since this is not using any closure, what is the purpose of having a delegate field instead of a (more readable) proper method?
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.
It's still capturing MyAddress
.
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'm confused. Is it really capturing the address? I would expect this code to capture this
and then accessing the MyAddress
field, which is no different to calling a proper instance method.
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're right, @jdom.
I would suggest creating a private readonly Predicate<SiloAddress>
field which captures this
once and passing that to the call here. That way you have no per-call allocations.
EDIT: We cannot simply capture the logic in a field since it requires excludeThisSiloIfStopping
which is a parameter to the calling function.
Inlining the logic of List<T>.FindLast
isn't so bad IMO
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's 2 things here. Since this is inlining FindLast, then there is not even a need to remove the delegate allocation, as there is no longer a closure being passed to another higher order function.
But even if we decided to continue using FindLast and remove the inlining, what I don't understand is the need for a function field that captures this
, since that is exactly what an instance method is. So just define the function as a method body instead of in a field that is later assigned.
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.
@jdom I agree with the first point.
For the second, when you pass an instance method as a delegate, like
membershipRingList.FindLast(this.SomePredicateMethod)
you are allocating a new Predicate<T>
every time.
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.
To double check, here's what I see in LINQPad:
void Main()
{
var list = new List<int>();
list.FindLast(this.GreaterThan2);
}
bool GreaterThan2(int value) => value > 2;
produce this IL:
IL_0000: nop
IL_0001: newobj System.Collections.Generic.List<System.Int32>..ctor
IL_0006: stloc.0 // list
IL_0007: ldloc.0 // list
IL_0008: ldarg.0
IL_0009: ldftn UserQuery.GreaterThan2
IL_000F: newobj System.Predicate<System.Int32>..ctor
IL_0014: callvirt System.Collections.Generic.List<System.Int32>.FindLast
IL_0019: pop
IL_001A: ret
GreaterThan2:
IL_0000: ldarg.1
IL_0001: ldc.i4.2
IL_0002: cgt
IL_0004: ret
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.
Indeed; fixed.
@@ -1088,6 +1098,11 @@ internal IRemoteGrainDirectory GetDirectoryReference(SiloAddress silo) | |||
return InsideRuntimeClient.Current.InternalGrainFactory.GetSystemTarget<IRemoteGrainDirectory>(Constants.DirectoryServiceId, silo); | |||
} | |||
|
|||
private bool IsSiloNextInTheRing(SiloAddress siloAddr, int hash, bool excludeMySelf) |
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.
This logic is same as other logic, except with <= rather than >=.
Please consider using one static function with both hashes in arguments, with the hash order changed, rather then two functions with the comparison reversed.
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 think that the resulting logic for this one-liner function will be more complex than now. If this is desired - I can change it.
In general, I've no objection to this change, but I don't really see much value in it ether. Unless I'm misunderstanding something, this call path is not nearly hot enough to merit this sort of micro-optimization. I could well be wrong though. |
@jason-bragg This allocation happens on almost every request message send. On the other hand GC induced by Orleans doesn't takes much CPU, so I don't expect any effect from one or two removed allocations. But in perspective the number of allocs on hot paths could be reduced to noticeable extent. |
@dVakulen, Agreed. I don't expect perf gain from this, but removing allocations and link expressions (notoriously slow) from the hot path can't hurt. LGTM |
Thank you, @dVakulen for pushing the throughput numbers higher with nearly every PR! |
@sergeybykov Just curious - was there any effect after merge of the #2275? |
* Remove closure allocation * Address feedback * Remove closure allocation in consistent ring provider
On each
Catalog
local lookup the delegate was allocated.