-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HeartbeatNodeRing performance (#4943)
* added benchmark for HeartbeatNodeRing performance * switched to local function No perf change * approve Akka.Benchmarks friend assembly for Akka.Cluster * remove HeartbeatNodeRing.NodeRing() allocation and make field immutable * made it so Akka.Util.Internal.ArrayExtensions.From no longer allocates (much) * added some descriptive comments on HeartbeatNodeRing.Receivers * Replaced `Lazy<T>` with `Option<T>` and a similar lazy initialization check Improved throughput by ~10% on larger collections and further reduced memory allocation. * changed return types to `IImmutableSet` Did this in order to reduce allocations from constantly converting back and forth from `ImmutableSortedSet<T>` and `ImmutableHashSet<T>` - that way we can just use whatever the underlying collection type is. * added ReachabilityBenchmarks
- Loading branch information
1 parent
ac07a0f
commit 234188e
Showing
8 changed files
with
258 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
src/benchmark/Akka.Benchmarks/Cluster/HeartbeatNodeRingBenchmarks.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Text; | ||
using Akka.Actor; | ||
using Akka.Benchmarks.Configurations; | ||
using Akka.Cluster; | ||
using BenchmarkDotNet.Attributes; | ||
using FluentAssertions; | ||
|
||
namespace Akka.Benchmarks.Cluster | ||
{ | ||
[Config(typeof(MicroBenchmarkConfig))] | ||
public class HeartbeatNodeRingBenchmarks | ||
{ | ||
[Params(10, 100, 250)] | ||
public int NodesSize; | ||
|
||
|
||
internal static HeartbeatNodeRing CreateHearbeatNodeRingOfSize(int size) | ||
{ | ||
var nodes = Enumerable.Range(1, size) | ||
.Select(x => new UniqueAddress(new Address("akka", "sys", "node-" + x, 2552), x)) | ||
.ToList(); | ||
var selfAddress = nodes[size / 2]; | ||
return new HeartbeatNodeRing(selfAddress, nodes.ToImmutableHashSet(), ImmutableHashSet<UniqueAddress>.Empty, 5); | ||
} | ||
|
||
private HeartbeatNodeRing _ring; | ||
|
||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
_ring = CreateHearbeatNodeRingOfSize(NodesSize); | ||
} | ||
|
||
private static void MyReceivers(HeartbeatNodeRing ring) | ||
{ | ||
var r = new HeartbeatNodeRing(ring.SelfAddress, ring.Nodes, ImmutableHashSet<UniqueAddress>.Empty, ring.MonitoredByNumberOfNodes); | ||
r.MyReceivers.Value.Count.Should().BeGreaterThan(0); | ||
} | ||
|
||
[Benchmark] | ||
[Arguments(1000)] | ||
public void HeartbeatNodeRing_should_produce_MyReceivers(int iterations) | ||
{ | ||
for(var i = 0; i < iterations; i++) | ||
MyReceivers(_ring); | ||
} | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
src/benchmark/Akka.Benchmarks/Cluster/ReachabilityBenchmarks.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Akka.Util; | ||
using Akka.Actor; | ||
using Akka.Benchmarks.Configurations; | ||
using Akka.Cluster; | ||
using BenchmarkDotNet.Attributes; | ||
using FluentAssertions; | ||
|
||
namespace Akka.Benchmarks.Cluster | ||
{ | ||
[Config(typeof(MicroBenchmarkConfig))] | ||
public class ReachabilityBenchmarks | ||
{ | ||
[Params(10, 100, 250)] | ||
public int NodesSize; | ||
|
||
[Params(100)] | ||
public int Iterations; | ||
|
||
public Address Address = new Address("akka", "sys", "a", 2552); | ||
public Address Node = new Address("akka", "sys", "a", 2552); | ||
|
||
private Reachability CreateReachabilityOfSize(Reachability baseReachability, int size) | ||
{ | ||
return Enumerable.Range(1, size).Aggregate(baseReachability, (r, i) => | ||
{ | ||
var obs = new UniqueAddress(Address.WithHost("node-" + i), i); | ||
var j = i == size ? 1 : i + 1; | ||
var subject = new UniqueAddress(Address.WithHost("node-" + j), j); | ||
return r.Unreachable(obs, subject).Reachable(obs, subject); | ||
}); | ||
} | ||
|
||
private Reachability AddUnreachable(Reachability baseReachability, int count) | ||
{ | ||
var observers = baseReachability.Versions.Keys.Take(count); | ||
// the Keys HashSet<T> IEnumerator does not support Reset, hence why we have to convert it to a list | ||
using var subjects = baseReachability.Versions.Keys.ToList().GetContinuousEnumerator(); | ||
return observers.Aggregate(baseReachability, (r, o) => | ||
{ | ||
return Enumerable.Range(1, 5).Aggregate(r, (r2, i) => | ||
{ | ||
subjects.MoveNext(); | ||
return r2.Unreachable(o, subjects.Current); | ||
}); | ||
}); | ||
} | ||
|
||
internal Reachability Reachability1; | ||
internal Reachability Reachability2; | ||
internal Reachability Reachability3; | ||
internal HashSet<UniqueAddress> Allowed; | ||
|
||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
Reachability1 = CreateReachabilityOfSize(Reachability.Empty, NodesSize); | ||
Reachability2 = CreateReachabilityOfSize(Reachability1, NodesSize); | ||
Reachability3 = AddUnreachable(Reachability1, NodesSize / 2); | ||
Allowed = Reachability1.Versions.Keys.ToHashSet(); | ||
} | ||
|
||
private void CheckThunkFor(Reachability r1, Reachability r2, Action<Reachability, Reachability> thunk, | ||
int times) | ||
{ | ||
for (var i = 0; i < times; i++) | ||
thunk(new Reachability(r1.Records, r1.Versions), new Reachability(r2.Records, r2.Versions)); | ||
} | ||
|
||
private void CheckThunkFor(Reachability r1, Action<Reachability> thunk, int times) | ||
{ | ||
for (var i = 0; i < times; i++) | ||
thunk(new Reachability(r1.Records, r1.Versions)); | ||
} | ||
|
||
private void Merge(Reachability r1, Reachability r2, int expectedRecords) | ||
{ | ||
r1.Merge(Allowed, r2).Records.Count.Should().Be(expectedRecords); | ||
} | ||
|
||
private void CheckStatus(Reachability r1) | ||
{ | ||
var record = r1.Records.First(); | ||
r1.Status(record.Observer, record.Subject).Should().Be(record.Status); | ||
} | ||
|
||
private void CheckAggregatedStatus(Reachability r1) | ||
{ | ||
var record = r1.Records.First(); | ||
r1.Status(record.Subject).Should().Be(record.Status); | ||
} | ||
|
||
private void AllUnreachableOrTerminated(Reachability r1) | ||
{ | ||
r1.AllUnreachableOrTerminated.IsEmpty.Should().BeFalse(); | ||
} | ||
|
||
private void AllUnreachable(Reachability r1) | ||
{ | ||
r1.AllUnreachable.IsEmpty.Should().BeFalse(); | ||
} | ||
|
||
private void RecordsFrom(Reachability r1) | ||
{ | ||
foreach (var o in r1.AllObservers) | ||
{ | ||
r1.RecordsFrom(o).Should().NotBeNull(); | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_merge_with_same_versions() | ||
{ | ||
CheckThunkFor(Reachability1, Reachability1, (r1, r2) => Merge(r1, r2, 0), Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_merge_with_all_older_versions() | ||
{ | ||
CheckThunkFor(Reachability2, Reachability1, (r1, r2) => Merge(r1, r2, 0), Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_merge_with_all_newer_versions() | ||
{ | ||
CheckThunkFor(Reachability1, Reachability2, (r1, r2) => Merge(r1, r2, 0), Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_merge_with_half_nodes_unreachable() | ||
{ | ||
CheckThunkFor(Reachability1, Reachability3, (r1, r2) => Merge(r1, r2, 5* NodesSize/2), Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_merge_with_half_nodes_unreachable_opposite() | ||
{ | ||
CheckThunkFor(Reachability3, Reachability1, (r1, r2) => Merge(r1, r2, 5 * NodesSize / 2), Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_check_status_with_half_nodes_unreachable() | ||
{ | ||
CheckThunkFor(Reachability3, CheckAggregatedStatus, Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_check_AllUnreachableOrTerminated_with_half_nodes_unreachable() | ||
{ | ||
CheckThunkFor(Reachability3, AllUnreachableOrTerminated, Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_check_AllUnreachable_with_half_nodes_unreachable() | ||
{ | ||
CheckThunkFor(Reachability3, AllUnreachable, Iterations); | ||
} | ||
|
||
[Benchmark] | ||
public void Reachability_must_check_RecordsFrom_with_half_nodes_unreachable() | ||
{ | ||
CheckThunkFor(Reachability3, RecordsFrom, Iterations); | ||
} | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
src/core/Akka.API.Tests/CoreAPISpec.ApproveCluster.approved.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.