Skip to content

Reduce FSM<TState, TData> allocations - #6145

Merged
Aaronontheweb merged 7 commits into
akkadotnet:v1.4from
Aaronontheweb:fsm-actor-allocations
Oct 7, 2022
Merged

Aaronontheweb merged 7 commits into
akkadotnet:v1.4from
Aaronontheweb:fsm-actor-allocations

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Oct 6, 2022 •

Copy link
Copy Markdown
Member

Changes

Reduced some unnecessary allocations inside the FSM<TState, TData> class.

Checklist

For significant changes, please ensure that the following have been completed (delete if not relevant):

Latest v1.4 Benchmarks

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.2006 (21H2)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.201
  [Host]     : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT
  DefaultJob : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT

Method MsgCount Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
BenchmarkFsm 1000000 772.8 ms 13.20 ms 12.35 ms 1.74 0.06 68000.0000 4000.0000 1000.0000 287 MB
BenchmarkUntyped 1000000 444.3 ms 8.88 ms 9.50 ms 1.00 0.00 12000.0000 4000.0000 1000.0000 56 MB

This PR's Benchmarks

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.2006 (21H2)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.201
  [Host]     : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT
  DefaultJob : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT

Method MsgCount Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
BenchmarkFsm 1000000 662.5 ms 12.99 ms 13.34 ms 1.47 0.05 35000.0000 4000.0000 1000.0000 150 MB
BenchmarkUntyped 1000000 453.5 ms 8.97 ms 11.35 ms 1.00 0.00 13000.0000 3000.0000 1000.0000 56 MB

48% memory reduction, 14% throughput improvement

@Aaronontheweb Aaronontheweb added akka-actor perf akka.net v1.4 Issues affecting Akka.NET v1.4 labels Oct 6, 2022
@Aaronontheweb Aaronontheweb added this to the 1.4.44 milestone Oct 6, 2022

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Described API and memory allocation changes.

public override string ToString() { }
}
public sealed class Event<TD> : Akka.Actor.INoSerializationVerificationNeeded
public struct Event<TD> : Akka.Actor.INoSerializationVerificationNeeded

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Technically a binary breaking change maybe, but it's source compatible.

public static Akka.Actor.FSMBase.StateTimeout Instance { get; }
}
public class State<TS, TD> : System.IEquatable<Akka.Actor.FSMBase.State<TS, TD>>
public sealed class State<TS, TD> : System.IEquatable<Akka.Actor.FSMBase.State<TS, TD>>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

sealed the State class for better perf.

public State(TS stateName, TD stateData, TimeSpan? timeout = null, Reason stopReason = null, IReadOnlyList<object> replies = null, bool notifies = true)
{
Replies = replies ?? new List<object>();
Replies = replies ?? Array.Empty<object>();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Eliminated unnecessary List<object> allocation.

internal State<TS, TD> WithNotification(bool notifies)
{
// don't bother allocating even a stack type if the notifies value is identical.
if (Notifies == notifies)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Don't return a new copy of this object if the current Notifies value matches the new one (it usually does.)

/// </summary>
/// <typeparam name="TD">The state data for this event</typeparam>
public sealed class Event<TD> : INoSerializationVerificationNeeded
public readonly struct Event<TD> : INoSerializationVerificationNeeded

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We allocate one of these for every message we process, so changing to a readonly struct here greatly reduces Gen 0 / 1 GC.

Comment thread src/core/Akka/Actor/FSM.cs Outdated
public State<TState, TData> Stay()
{
return GoTo(_currentState.StateName).WithNotification(false);
return _currentState.WithNotification(false);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Biggest memory savings - don't allocate a new State object each time we call Stay. Re-use the same value.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

FWIW, there's no benefit to copying the same state over and over again here. The Reason, Replies properties et al don't have any effects when used here in combination with WithNotification(false).

@Aaronontheweb

Copy link
Copy Markdown
Member Author

Looked at making State<TData, TState> into a struct as well but that's a significant breaking API change (we have lots of FSM behaviors that return null in order to trigger Unhandled behavior) that doesn't yield much benefit once we stop returning new states during Stay() operations.

@Aaronontheweb

Copy link
Copy Markdown
Member Author

Looks like this change definitely caused an issue with the FSMTimingSpec. Going to need to look at that before I ask for review.

@Aaronontheweb

Copy link
Copy Markdown
Member Author

Looks like this change definitely caused an issue with the FSMTimingSpec. Going to need to look at that before I ask for review.

Spec could also be racy - worked on my machine.

@Aaronontheweb

Copy link
Copy Markdown
Member Author

Looks like this change definitely caused an issue with the FSMTimingSpec. Going to need to look at that before I ask for review.

Spec could also be racy - worked on my machine.

Nope, definitely an error related to my changes. Will investigate.

@Aaronontheweb

Copy link
Copy Markdown
Member Author

Benchmarks based on latest changes.

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.2006 (21H2)
AMD Ryzen 7 1700, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.201
  [Host]     : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT
  DefaultJob : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT

Method MsgCount Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
BenchmarkFsm 1000000 641.1 ms 12.73 ms 14.15 ms 1.35 0.05 35000.0000 4000.0000 1000.0000 150 MB
BenchmarkUntyped 1000000 474.1 ms 9.38 ms 10.04 ms 1.00 0.00 12000.0000 4000.0000 1000.0000 56 MB

@Arkatufus Arkatufus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) October 7, 2022 15:28
@Aaronontheweb
Aaronontheweb disabled auto-merge October 7, 2022 15:59
@Aaronontheweb
Aaronontheweb merged commit 234bb8f into akkadotnet:v1.4 Oct 7, 2022
@Aaronontheweb
Aaronontheweb deleted the fsm-actor-allocations branch October 7, 2022 15:59
Aaronontheweb added a commit to Aaronontheweb/akka.net that referenced this pull request Oct 8, 2022
* close akkadotnet#2560 - added performance benchmarks for FSM

* Improved FSM memory consumption

* Made `Event` a `readonly struct`
* Eliminated unnecessary `List<object>` allocations
* Cleaned up XML-DOC comments

* don't return new `State<TState, TData>` during `Stay()`

* API approvals

* fixed `State<TS.,TD>` errors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

akka.net v1.4 Issues affecting Akka.NET v1.4 akka-actor perf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants