]> git.deb.at Git - rhonda/impostor.git/commitdiff
Remove EventManager CallAsync memory allocation
authorAeonLucid <aeonlucid@outlook.com>
Mon, 26 Oct 2020 23:40:10 +0000 (00:40 +0100)
committerAeonLucid <aeonlucid@outlook.com>
Mon, 26 Oct 2020 23:40:10 +0000 (00:40 +0100)
src/Impostor.Benchmarks/.gitignore [new file with mode: 0644]
src/Impostor.Benchmarks/Impostor.Benchmarks.csproj [new file with mode: 0644]
src/Impostor.Benchmarks/Program.cs [new file with mode: 0644]
src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs [new file with mode: 0644]
src/Impostor.Server/Events/EventManager.cs
src/Impostor.Server/Properties/AssemblyInfo.cs
src/Impostor.sln

diff --git a/src/Impostor.Benchmarks/.gitignore b/src/Impostor.Benchmarks/.gitignore
new file mode 100644 (file)
index 0000000..1c2dac6
--- /dev/null
@@ -0,0 +1 @@
+BenchmarkDotNet.Artifacts
\ No newline at end of file
diff --git a/src/Impostor.Benchmarks/Impostor.Benchmarks.csproj b/src/Impostor.Benchmarks/Impostor.Benchmarks.csproj
new file mode 100644 (file)
index 0000000..697de52
--- /dev/null
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="BenchmarkDotNet" Version="0.12.1"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj"/>
+  </ItemGroup>
+
+</Project>
diff --git a/src/Impostor.Benchmarks/Program.cs b/src/Impostor.Benchmarks/Program.cs
new file mode 100644 (file)
index 0000000..213070f
--- /dev/null
@@ -0,0 +1,18 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Running;
+using Impostor.Benchmarks.Tests;
+
+namespace Impostor.Benchmarks
+{
+    internal static class Program
+    {
+        private static void Main(string[] args)
+        {
+            BenchmarkRunner.Run<EventManagerBenchmark>(
+                DefaultConfig.Instance
+                    .AddDiagnoser(MemoryDiagnoser.Default)
+            );
+        }
+    }
+}
diff --git a/src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs b/src/Impostor.Benchmarks/Tests/EventManagerBenchmark.cs
new file mode 100644 (file)
index 0000000..4470d92
--- /dev/null
@@ -0,0 +1,77 @@
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Managers;
+using Impostor.Server.Events;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Impostor.Benchmarks.Tests
+{
+    public class EventManagerBenchmark
+    {
+        private IEventManager _eventManager;
+        private IGameEvent _event;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            var services = new ServiceCollection();
+
+            services.AddLogging();
+            services.AddSingleton<IEventManager, EventManager>();
+
+            _event = new GameStartedEvent(null);
+            _eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
+            _eventManager.RegisterListener(new EventListener());
+            _eventManager.RegisterListener(new EventListener());
+            _eventManager.RegisterListener(new EventListener());
+            _eventManager.RegisterListener(new EventListener());
+            _eventManager.RegisterListener(new EventListener());
+        }
+
+        [Benchmark]
+        public async Task Run_1()
+        {
+            for (var i = 0; i < 1; i++)
+            {
+                await _eventManager.CallAsync(_event);
+            }
+        }
+
+        [Benchmark]
+        public async Task Run_1000()
+        {
+            for (var i = 0; i < 1000; i++)
+            {
+                await _eventManager.CallAsync(_event);
+            }
+        }
+
+        [Benchmark]
+        public async Task Run_10000()
+        {
+            for (var i = 0; i < 10000; i++)
+            {
+                await _eventManager.CallAsync(_event);
+            }
+        }
+
+        [Benchmark]
+        public async Task Run_100000()
+        {
+            for (var i = 0; i < 100000; i++)
+            {
+                await _eventManager.CallAsync(_event);
+            }
+        }
+
+        private class EventListener : IEventListener
+        {
+            [EventListener]
+            public void OnGameStarted(IGameStartedEvent e)
+            {
+
+            }
+        }
+    }
+}
index 10c3ec892517b91dfcd74241b86173937fb0662c..df8829f4897976b6618b3aa55a0ded4a30ebb910 100644 (file)
@@ -1,8 +1,7 @@
-using System;
+using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
-using System.Reflection;
 using System.Threading.Tasks;
 using Impostor.Api;
 using Impostor.Api.Events;
@@ -16,6 +15,7 @@ namespace Impostor.Server.Events
     internal class EventManager : IEventManager
     {
         private readonly ConcurrentDictionary<Type, TemporaryEventRegister> _temporaryEventListeners;
+        private readonly ConcurrentDictionary<Type, List<EventHandler>> _cachedEventHandlers;
         private readonly ILogger<EventManager> _logger;
         private readonly IServiceProvider _serviceProvider;
 
@@ -24,6 +24,7 @@ namespace Impostor.Server.Events
             _logger = logger;
             _serviceProvider = serviceProvider;
             _temporaryEventListeners = new ConcurrentDictionary<Type, TemporaryEventRegister>();
+            _cachedEventHandlers = new ConcurrentDictionary<Type, List<EventHandler>>();
         }
 
         /// <inheritdoc />
@@ -54,6 +55,11 @@ namespace Impostor.Server.Events
                 register.Add(wrappedEventListener);
             }
 
+            if (eventListeners.Count > 0)
+            {
+                _cachedEventHandlers.TryRemove(typeof(TListener), out _);
+            }
+
             return new MultiDisposable(disposes);
         }
 
@@ -61,7 +67,12 @@ namespace Impostor.Server.Events
         public bool IsRegistered<TEvent>()
             where TEvent : IEvent
         {
-            return GetHandlers<TEvent>(_serviceProvider).Any();
+            if (_cachedEventHandlers.TryGetValue(typeof(TEvent), out var handlers))
+            {
+                return handlers.Count > 0;
+            }
+
+            return GetHandlers<TEvent>().Any();
         }
 
         /// <inheritdoc />
@@ -70,8 +81,12 @@ namespace Impostor.Server.Events
         {
             try
             {
-                foreach (var (handler, eventListener) in GetHandlers<T>(_serviceProvider)
-                    .OrderByDescending(e => e.Listener.Priority))
+                if (!_cachedEventHandlers.TryGetValue(typeof(T), out var handlers))
+                {
+                    handlers = CacheEventHandlers<T>();
+                }
+
+                foreach (var (handler, eventListener) in handlers)
                 {
                     await eventListener.InvokeAsync(handler, @event, _serviceProvider);
                 }
@@ -86,12 +101,23 @@ namespace Impostor.Server.Events
             }
         }
 
+        private List<EventHandler> CacheEventHandlers<TEvent>()
+            where TEvent : IEvent
+        {
+            var handlers = GetHandlers<TEvent>()
+                .OrderByDescending(e => e.Listener.Priority)
+                .ToList();
+
+            _cachedEventHandlers[typeof(TEvent)] = handlers;
+
+            return handlers;
+        }
+
         /// <summary>
         ///     Get all the event listeners for the given event type.
         /// </summary>
-        /// <param name="services">Current service provider.</param>
         /// <returns>The event listeners.</returns>
-        private IEnumerable<EventHandler> GetHandlers<TEvent>(IServiceProvider services)
+        private IEnumerable<EventHandler> GetHandlers<TEvent>()
             where TEvent : IEvent
         {
             var eventType = typeof(TEvent);
@@ -108,7 +134,7 @@ namespace Impostor.Server.Events
                 }
             }
 
-            foreach (var handler in services.GetServices<IEventListener>())
+            foreach (var handler in _serviceProvider.GetServices<IEventListener>())
             {
                 if (handler is IManualEventListener manualEventListener && manualEventListener.CanExecute<TEvent>())
                 {
index 99d6f86e8473ce101a39f266610bf34ae72530a6..84c51585ac7a296d6836fa0b012bfd8f93b451d4 100644 (file)
@@ -1,4 +1,5 @@
 using System.Runtime.CompilerServices;
 
+[assembly:InternalsVisibleTo("Impostor.Benchmarks")]
 [assembly:InternalsVisibleTo("Impostor.Tests")]
-[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")]
\ No newline at end of file
+[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")]
index 9ec2b61f166711661dab004c1ba02a0fe360e6c6..b9706de66679e48a5eb6af72910e15edd0737d34 100644 (file)
@@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Tools.ServerReplay
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Example", "Impostor.Plugins.Example\Impostor.Plugins.Example.csproj", "{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Benchmarks", "Impostor.Benchmarks\Impostor.Benchmarks.csproj", "{EA04E386-6CCB-4C52-8C82-64F32C7EB377}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -146,6 +148,14 @@ Global
                {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.Build.0 = Release|Any CPU
                {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.ActiveCfg = Release|Any CPU
                {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.Build.0 = Release|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|x86.Build.0 = Debug|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|Any CPU.Build.0 = Release|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|x86.ActiveCfg = Release|Any CPU
+               {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE