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

Remove delegate allocation #2312

Merged
merged 3 commits into from Oct 20, 2016
Merged

Conversation

dVakulen
Copy link
Contributor

On each Catalog local lookup the delegate was allocated.

@@ -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
Copy link
Contributor Author

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));
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

@jdom jdom Oct 17, 2016

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.

Copy link
Member

Choose a reason for hiding this comment

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

image

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

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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      

Copy link
Contributor Author

@dVakulen dVakulen Oct 18, 2016

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@jason-bragg
Copy link
Contributor

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.

@dVakulen
Copy link
Contributor Author

@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.

@jason-bragg
Copy link
Contributor

@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

@jason-bragg jason-bragg merged commit 04a3b19 into dotnet:master Oct 20, 2016
@dVakulen dVakulen deleted the allocs-removal-2 branch October 20, 2016 08:44
@sergeybykov
Copy link
Contributor

Thank you, @dVakulen for pushing the throughput numbers higher with nearly every PR!

@dVakulen
Copy link
Contributor Author

dVakulen commented Oct 21, 2016

@sergeybykov Just curious - was there any effect after merge of the #2275?

@ReubenBond
Copy link
Member

@dVakulen there was an approx 6% increase by my reckoning. It's hard to be sure that it was caused by #2275 specifically, particularly because #2295 went in at about the same time.

sergeybykov pushed a commit to sergeybykov/orleans that referenced this pull request Oct 27, 2016
* Remove closure allocation

* Address feedback

* Remove closure allocation in consistent ring provider
@github-actions github-actions bot locked and limited conversation to collaborators Dec 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants