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

ReceiveActor - Removing MatchBuilder and making use of handlers. #7498

Open
wants to merge 7 commits into
base: v1.6
Choose a base branch
from

Conversation

mhbuck
Copy link
Contributor

@mhbuck mhbuck commented Feb 9, 2025

Fixes #
This is part of the changed needed to resolve #2811

Changes

Here the changes are to not make use of the match builder and just make use of the handlers. The approach I have taken is very similar in structure to what was existing but making use of handlers for the various types. There could be merit in removing more of the logic to simplify the class more.

This approach has some internal classes (I created as internal as an initial approach I can see merit in pulling these out to be more clean within the class.

While all of these tests do pass I do think there are some edge cases that this does not cover when reading the comments in the MatchBuilder class. If the general approach of the handlers is what is wanted I will go into the comments and edge cases in more detail.

Checklist

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

Latest dev Benchmarks

Note I pushed the dotnet up to 8.0 because I did not have 6.0 installed on this machine

BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.5371/22H2/2022Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 9.0.102
  [Host]     : .NET 8.0.12 (8.0.1224.60305), X64 RyuJIT AVX2
  Job-WBRIAI : .NET 8.0.12 (8.0.1224.60305), X64 RyuJIT AVX2

InvocationCount=1  LaunchCount=10  RunStrategy=Monitoring
UnrollFactor=1  WarmupCount=10

| Method                                | Mean     | Error   | StdDev  | Req/sec      |
|-------------------------------------- |---------:|--------:|--------:|-------------:|
| Actor_ping_pong_single_pair_in_memory | 227.4 ns | 1.37 ns | 4.04 ns | 4 397 699,09 |

Warming up...
OSVersion:              Microsoft Windows NT 10.0.19045.0
ProcessorCount:         8
ClockSpeed:             0 MHZ
Actor Count:            16
Messages sent/received: 30000000  (3e7)
Is Server GC:           True
Thread count:           26

ActorBase    first start time: 10.46 ms
ReceiveActor first start time: 16.52 ms

            ActorBase                          ReceiveActor
Throughput, Msgs/sec, Start [ms], Total [ms],  Msgs/sec, Start [ms], Total [ms]
         1, 25210000,      14.80,    1205.11,  28409000,      88.74,    1144.76
         5, 40595000,       7.58,     747.50,  39370000,       2.97,     765.10
        10, 46583000,     165.06,     809.82,  46511000,       2.32,     647.37
        15, 57142000,       2.29,     528.20,  54347000,       2.41,     555.26
        20, 52264000,     100.35,     674.69,  47021000,       3.83,     642.75
        30, 51107000,       1.41,     588.55,  49180000,      40.18,     651.06
        40, 59055000,       1.35,     509.36,  47846000,       1.41,     628.67
        50, 50847000,       1.92,     592.04,  58593000,       2.31,     514.41
        60, 55970000,       2.12,     539.09,  52447000,      19.28,     591.90
        70, 58939000,       1.37,     510.65,  54151000,       1.39,     555.68
        80, 57915000,       1.41,     519.77,  56179000,      12.66,     547.47
        90, 57251000,       1.37,     526.20,  48231000,       1.37,     623.77
       100, 64239000,       1.26,     468.96,  60000000,       1.39,     501.52
       200, 58593000,       1.47,     513.87,  52083000,       1.94,     578.61
       300, 64935000,       1.58,     464.56,  57915000,       1.56,     519.81
       400, 57251000,       4.82,     529.21,  56074000,       1.39,     536.56
       500, 63559000,       1.89,     474.61,  47694000,       1.27,     630.63
       600, 64377000,       1.29,     468.21,  60975000,       1.39,     493.41
       700, 57581000,       1.23,     522.33,  61728000,       1.27,     488.07
       800, 62630000,       1.27,     480.66,  56925000,       1.27,     528.49
       900, 62630000,       1.26,     481.06,  51724000,       1.25,     582.17

This PR's Benchmarks

So as the numbers are showing the performance has gone backwards

BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.5371/22H2/2022Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 9.0.102
  [Host]     : .NET 8.0.12 (8.0.1224.60305), X64 RyuJIT AVX2
  Job-WLSTQM : .NET 8.0.12 (8.0.1224.60305), X64 RyuJIT AVX2

InvocationCount=1  LaunchCount=10  RunStrategy=Monitoring
UnrollFactor=1  WarmupCount=10

| Method                                | Mean     | Error   | StdDev  | Req/sec      |
|-------------------------------------- |---------:|--------:|--------:|-------------:|
| Actor_ping_pong_single_pair_in_memory | 236.5 ns | 1.77 ns | 5.22 ns | 4 228 340,67 |

Warming up...
OSVersion:              Microsoft Windows NT 10.0.19045.0
ProcessorCount:         8
ClockSpeed:             0 MHZ
Actor Count:            16
Messages sent/received: 30000000  (3e7)
Is Server GC:           True
Thread count:           26

ActorBase    first start time: 11.96 ms
ReceiveActor first start time:  5.37 ms

            ActorBase                          ReceiveActor
Throughput, Msgs/sec, Start [ms], Total [ms],  Msgs/sec, Start [ms], Total [ms]
         1, 22692000,       5.69,    1328.11,  28544000,      44.97,    1096.91
         5, 44247000,     156.34,     834.79,  39421000,       3.50,     765.35
        10, 57692000,       2.62,     523.27,  40983000,      33.38,     765.45
        15, 52356000,      12.17,     585.59,  52631000,     145.33,     716.11
        20, 62240000,       3.25,     486.14,  51282000,       4.10,     589.88
        30, 62893000,       1.72,     479.48,  44247000,       9.63,     688.59
        40, 63965000,       2.06,     471.63,  53003000,       1.58,     568.51
        50, 63424000,      18.76,     492.70,  48154000,       1.57,     625.47
        60, 67567000,       1.38,     445.43,  53571000,       1.56,     561.95
        70, 59405000,       1.42,     506.46,  54945000,       1.40,     548.33
        80, 66371000,       1.38,     454.23,  55045000,       2.75,     548.59
        90, 61983000,       1.28,     485.55,  55248000,       1.53,     544.57
       100, 65502000,       1.35,     460.24,  52356000,       1.49,     574.49
       200, 67873000,       1.93,     444.42,  55970000,       1.49,     537.89
       300, 63694000,       1.63,     472.91,  54347000,      62.60,     614.65
       400, 63965000,       1.74,     471.53,  51194000,       1.36,     587.42
       500, 68807000,       1.39,     438.34,  52447000,       1.46,     573.91
       600, 58479000,       1.29,     514.95,  54844000,       1.60,     548.89
       700, 53956000,       1.30,     557.95,  46296000,       1.85,     650.38
       800, 62111000,       2.65,     486.63,  51282000,       1.61,     587.52
       900, 65075000,       1.69,     463.57,  46224000,       1.95,     651.46

Copy link
Contributor Author

@mhbuck mhbuck left a comment

Choose a reason for hiding this comment

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

This was my approach that does work but has some work I feel. There are definitely some edge cases that still need to be checked before finalising.

// Moved out and better tested
class HandlerSet
{
public HandlerSet()
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 idea here is having something that handles the calls for each of the states of the actor.

  1. TypedHandlers - Handlers when we have the type information
  2. HandleObject - Handles calls when the type information supplied is object
  3. Handle Any - The catch all handler

Now that I am reviewing this I feel like the naming should be a bit more consistent

}

// Attempting to make more use of generics to avoid boxing
class TypeHandler<T> : ITypeHandler
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have an interface that impliments some methods that can be used during the playback of messages. This was seemingly the nicest way to be able to handle generics and handle object calls.

Copy link
Member

Choose a reason for hiding this comment

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

Pattern makes sense to me.

That said, I wonder if A more aggressive 'TryHandle(object message)` is warranted (i.e. to rely less on PGO/etc to avoid multiple unboxes) but benchmarks can be used to check benefit if any.

handlers.TypedHandlers[typeof(T)] = handlerList;
}

var typeHandler = new TypeHandler<T>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handlers where the Generic types are present and can be used


if (typeof(T) == typeof(object))
{
handlers.HandleObject.Add(new TypeHandler<object>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the type supplied is object needed to make use of an object type handler. This is one of the things that I was not a big fan of but could not think of a more elegant way of doing it.

}

// Have to use object here as dont have the generic type information
var typeHandler = new TypeHandler<object>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the generic information is not present here we needed to resort to using the object type.

@Aaronontheweb
Copy link
Member

@mhbuck I'm getting 351 test failures here so there might be an issue with the way we're doing message handling under this PR

@mhbuck
Copy link
Contributor Author

mhbuck commented Feb 10, 2025

Will expand running the tests locally and narrow it down. 👍

@mhbuck
Copy link
Contributor Author

mhbuck commented Feb 10, 2025

@mhbuck I'm getting 351 test failures here so there might be an issue with the way we're doing message handling under this PR

I am pretty sure it is down to handling of concrete types vs interfaces etc. I took a pretty naive approach for handling. I will have a look into the current logic and work out a better approach.

Copy link
Member

@to11mtm to11mtm left a comment

Choose a reason for hiding this comment

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

Left some initial feedback, need to pull it down and look further though.

Impressive at first look!

Edited to add:

Didn't look at benchmark numbers first, Now that I have that gives me some things to dig deeper into. I think some of the feedback may help, It may also be worth looking at profiling to see where hotspots are and how those can be mitigated.

HandleObject = new List<ITypeHandler>();
}

public Dictionary<Type, List<ITypeHandler>> TypedHandlers { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Where this can get tricky, is that I am not sure whether the overall implementation has the same 'inheritance/execution rules' as existing MatchBuilder.

A fun test case would be

public interface IFoo
{
}

public class FooA : IFoo
{
}
public class FooB : IFoo
{
}
///

Receive<IFoo>(/**/);
Receive<FooA>(/**/);
Receive<FooB>(/**/);

possibly also playing with predicate rules.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah this scenario is what is breaking most of the tests in clustering because I believe it makes use of this. I am working on building out the tests to cover the inheritance cases and get a better understanding the current matchbuilder functionality and where my approach is lacking. It will also make the receive actor tests more robust.

These tests are what I need to dig into more and understand better https://github.com/akkadotnet/akka.net/tree/dev/src/core/Akka.Tests/MatchHandler

HandleObject = new List<ITypeHandler>();
}

public Dictionary<Type, List<ITypeHandler>> TypedHandlers { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

But also, any reasons for not having it be a get-only? I get that HandleAny can mutate but not sure about this one (or HandleObject

}

// Attempting to make more use of generics to avoid boxing
class TypeHandler<T> : ITypeHandler
Copy link
Member

Choose a reason for hiding this comment

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

Pattern makes sense to me.

That said, I wonder if A more aggressive 'TryHandle(object message)` is warranted (i.e. to rely less on PGO/etc to avoid multiple unboxes) but benchmarks can be used to check benefit if any.

{
var wasHandled = partialAction(message);
if(!wasHandled && _shouldUnhandle)
var messageType = message.GetType();
Copy link
Member

Choose a reason for hiding this comment

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

this should probably get inlined into the TryGetValue (or if we need a different lookup call).

currentHandler.HandleAny(message);
}

if (_shouldUnhandle)
Copy link
Member

Choose a reason for hiding this comment

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

Might be a stupid question, but should this also get hit if HandleAny is null? (or maybe other logic concern?)

@Aaronontheweb Aaronontheweb added akka-actor AOT Ahead-of-Time (AOT) Compilation labels Feb 14, 2025
This is probably not the most ideal code. I want to make sure the tests are passing better before getting to cleaning it up
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
akka-actor AOT Ahead-of-Time (AOT) Compilation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants