{"id":5586,"date":"2018-11-25T14:00:14","date_gmt":"2018-11-25T14:00:14","guid":{"rendered":"http:\/\/writeasync.net\/?p=5586"},"modified":"2018-11-24T19:55:05","modified_gmt":"2018-11-24T19:55:05","slug":"a-simple-message-bus-in-three-languages","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5586","title":{"rendered":"A simple message bus"},"content":{"rendered":"<p>The <a href=\"https:\/\/www.enterpriseintegrationpatterns.com\/patterns\/messaging\/MessageBus.html\">message bus<\/a> is a typical pattern to allow loosely coupled software components to communicate, usually in an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Event-driven_architecture\">event-driven<\/a> manner. Don&#8217;t let the <a href=\"http:\/\/thedailywtf.com\/articles\/The-Enterprise-Dependency\">enterprise-y<\/a> description deter you &#8212; a simple message bus for a single process scenario can be quite easy to build. Let&#8217;s look at two different implementations in C# and C++.<\/p>\n<p>First, some simplifying assumptions:<\/p>\n<ul>\n<li>No <a href=\"http:\/\/openmessaging.blogspot.com\/2009\/04\/durable-messages-and-persistent.html\">durability or persistence<\/a>.<\/li>\n<li>Subscribers never unsubscribe.<\/li>\n<li>No thread safety.<\/li>\n<\/ul>\n<p>These sound like terrible limitations, but remember that we are looking at a single process scenario. In such cases, it is not only fine but often expected to wire up all subscriptions on application startup (prior to all sends) and to drop\/forget any messages in flight on shutdown.<\/p>\n<p>Let&#8217;s start with the C# implementation. First, as always, the unit tests:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    using System;\r\n    using System.Collections.Generic;\r\n    using FluentAssertions;\r\n    using Microsoft.VisualStudio.TestTools.UnitTesting;\r\n\r\n    &#x5B;TestClass]\r\n    public class MessageBusTest\r\n    {\r\n        &#x5B;TestMethod]\r\n        public void SendZeroSubscribers()\r\n        {\r\n            MessageBus bus = new MessageBus();\r\n\r\n            Action act = () =&gt; bus.Send(new MyMessage(&quot;hello&quot;));\r\n\r\n            act.Should().NotThrow();\r\n        }\r\n\r\n        &#x5B;TestMethod]\r\n        public void SendOneSubscriber()\r\n        {\r\n            MessageBus bus = new MessageBus();\r\n            List&lt;string&gt; received = new List&lt;string&gt;();\r\n            Action&lt;MyMessage&gt; subscriber = m =&gt; received.Add(m.Text);\r\n\r\n            bus.Subscribe(subscriber);\r\n            bus.Send(new MyMessage(&quot;hello&quot;));\r\n\r\n            received.Should().ContainSingle().Which.Should().Be(&quot;hello&quot;);\r\n        }\r\n\r\n        &#x5B;TestMethod]\r\n        public void SendTwoSubscribers()\r\n        {\r\n            MessageBus bus = new MessageBus();\r\n            List&lt;string&gt; received = new List&lt;string&gt;();\r\n            Action&lt;MyMessage&gt; subscriber1 = m =&gt; received.Add(m.Text + &quot;1&quot;);\r\n            Action&lt;MyMessage&gt; subscriber2 = m =&gt; received.Add(m.Text + &quot;2&quot;);\r\n\r\n            bus.Subscribe(subscriber1);\r\n            bus.Subscribe(subscriber2);\r\n            bus.Send(new MyMessage(&quot;hello&quot;));\r\n\r\n            received.Should().HaveCount(2).And.ContainInOrder(&quot;hello1&quot;, &quot;hello2&quot;);\r\n        }\r\n\r\n        &#x5B;TestMethod]\r\n        public void SendTwoOneSubscriberEach()\r\n        {\r\n            MessageBus bus = new MessageBus();\r\n            List&lt;string&gt; received = new List&lt;string&gt;();\r\n            Action&lt;MyMessage&gt; subscriber1 = m =&gt; received.Add(m.Text + &quot;1&quot;);\r\n            Action&lt;MyOtherMessage&gt; subscriber2 = m =&gt; received.Add(m.Text + &quot;2&quot;);\r\n\r\n            bus.Subscribe(subscriber1);\r\n            bus.Subscribe(subscriber2);\r\n            bus.Send(new MyMessage(&quot;one-hello&quot;));\r\n            bus.Send(new MyOtherMessage(&quot;two-hello&quot;));\r\n\r\n            received.Should().HaveCount(2).And.ContainInOrder(&quot;one-hello1&quot;, &quot;two-hello2&quot;);\r\n        }\r\n\r\n        &#x5B;TestMethod]\r\n        public void SendTwoSimpleTypesOneSubscriberEach()\r\n        {\r\n            MessageBus bus = new MessageBus();\r\n            List&lt;string&gt; received = new List&lt;string&gt;();\r\n            Action&lt;string&gt; subscriber1 = m =&gt; received.Add(&quot;S=&quot; + m);\r\n            Action&lt;int&gt; subscriber2 = m =&gt; received.Add(&quot;N=&quot; + m);\r\n\r\n            bus.Subscribe(subscriber1);\r\n            bus.Subscribe(subscriber2);\r\n            bus.Send(&quot;xyz&quot;);\r\n            bus.Send(123);\r\n\r\n            received.Should().HaveCount(2).And.ContainInOrder(&quot;S=xyz&quot;, &quot;N=123&quot;);\r\n        }\r\n\r\n        &#x5B;TestMethod]\r\n        public void SendTwoInstancesThreeSubscribersEachBeforeAndAfter()\r\n        {\r\n            MessageBus bus1 = new MessageBus();\r\n            MessageBus bus2 = new MessageBus();\r\n            List&lt;string&gt; received = new List&lt;string&gt;();\r\n            Action&lt;string&gt; subscriber1 = m =&gt; received.Add(&quot;S1=&quot; + m);\r\n            Action&lt;string&gt; subscriber2 = m =&gt; received.Add(&quot;S2=&quot; + m);\r\n            Action&lt;string&gt; subscriber3 = m =&gt; received.Add(&quot;S3=&quot; + m);\r\n            Action&lt;string&gt; subscriber4 = m =&gt; received.Add(&quot;S4=&quot; + m);\r\n            Action&lt;int&gt; subscriber5 = m =&gt; received.Add(&quot;S5=&quot; + m);\r\n            Action&lt;int&gt; subscriber6 = m =&gt; received.Add(&quot;S6=&quot; + m);\r\n\r\n            bus1.Subscribe(subscriber1);\r\n            bus2.Subscribe(subscriber2);\r\n            bus1.Send(&quot;aaa&quot;);\r\n            bus2.Send(&quot;bbb&quot;);\r\n            bus1.Subscribe(subscriber3);\r\n            bus2.Subscribe(subscriber4);\r\n            bus1.Send(&quot;ccc&quot;);\r\n            bus2.Send(&quot;ddd&quot;);\r\n            bus1.Subscribe(subscriber5);\r\n            bus2.Subscribe(subscriber6);\r\n            bus1.Send(1);\r\n            bus2.Send(2);\r\n\r\n            received.Should().HaveCount(8).And.ContainInOrder(\r\n                &quot;S1=aaa&quot;, &quot;S2=bbb&quot;, &quot;S1=ccc&quot;, &quot;S3=ccc&quot;, &quot;S2=ddd&quot;, &quot;S4=ddd&quot;, &quot;S5=1&quot;, &quot;S6=2&quot;);\r\n        }\r\n\r\n        private class MyMessage\r\n        {\r\n            public MyMessage(string text)\r\n            {\r\n                this.Text = text;\r\n            }\r\n\r\n            public string Text { get; }\r\n        }\r\n\r\n        private class MyOtherMessage : MyMessage\r\n        {\r\n            public MyOtherMessage(string text)\r\n                : base(text)\r\n            {\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>Note that the subscribers here are simple strongly-typed generic delegates and that the messages can be any type. The following implementation satisfies these tests:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    using System;\r\n    using System.Collections.Generic;\r\n\r\n    public sealed class MessageBus\r\n    {\r\n        private readonly Dictionary&lt;Type, Action&lt;object&gt;&gt; subscribers;\r\n\r\n        public MessageBus()\r\n        {\r\n            this.subscribers = new Dictionary&lt;Type, Action&lt;object&gt;&gt;();\r\n        }\r\n\r\n        public void Subscribe&lt;TMessage&gt;(Action&lt;TMessage&gt; subscriber)\r\n        {\r\n            Type key = typeof(TMessage);\r\n            if (!this.subscribers.ContainsKey(key))\r\n            {\r\n                this.subscribers.Add(key, null);\r\n            }\r\n\r\n            this.subscribers&#x5B;key] += m =&gt; subscriber((TMessage)m);\r\n        }\r\n\r\n        public void Send&lt;TMessage&gt;(TMessage message)\r\n        {\r\n            if (this.subscribers.TryGetValue(typeof(TMessage), out Action&lt;object&gt; action))\r\n            {\r\n                action(message);\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>The code has the luxury of being very concise here in part due to the hidden bonus feature of .NET delegates &#8212; <a href=\"https:\/\/docs.microsoft.com\/en-us\/previous-versions\/visualstudio\/visual-studio-2008\/ff652506(v=orm.10)\">they all support multicast by default<\/a>. We are also using the generic type parameter to our advantage, as the basis of the lookup key. The only slightly clunky part is that <code>(TMessage)<\/code> cast but I can&#8217;t think of a way around it.<\/p>\n<p>Let&#8217;s move on to C++. We don&#8217;t have reflection here, so an equivalent solution will have to either use <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/cpp\/run-time-type-information?view=vs-2017\">RTTI<\/a> or some fancy compile-time template trick. I&#8217;ve opted for the latter, which I&#8217;ll discuss in a minute. First, we need to port the unit tests to their equivalents using the <a href=\"https:\/\/blogs.msdn.microsoft.com\/vcblog\/2017\/04\/19\/cpp-testing-in-visual-studio\/\">Microsoft Native C++ Unit Test Framework<\/a>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ MessageBusTest.cpp\r\n\r\n#include &quot;CppUnitTest.h&quot;\r\n#include &quot;MessageBus.h&quot;\r\n\r\n#include &lt;sstream&gt;\r\n#include &lt;vector&gt;\r\n\r\nusing namespace Microsoft::VisualStudio::CppUnitTestFramework;\r\nusing namespace std;\r\n\r\nnamespace Test\r\n{\r\n    TEST_CLASS(MessageBusTest)\r\n    {\r\n    public:\r\n        TEST_METHOD(SendZeroSubscribers)\r\n        {\r\n            bool threwException = false;\r\n            MessageBus bus;\r\n\r\n            try\r\n            {\r\n                bus.Send(MyMessage(&quot;hello&quot;));\r\n            }\r\n            catch (...)\r\n            {\r\n                threwException = true;\r\n            }\r\n\r\n            Assert::IsFalse(threwException);\r\n        }\r\n\r\n        TEST_METHOD(SendOneSubscriber)\r\n        {\r\n            MessageBus bus;\r\n            vector&lt;string&gt; received;\r\n            auto subscriber = &#x5B;&amp;received](MyMessage&amp; m) { received.push_back(m.get_Text()); };\r\n\r\n            bus.Subscribe&lt;MyMessage&gt;(subscriber);\r\n            bus.Send(MyMessage(&quot;hello&quot;));\r\n\r\n            Assert::AreEqual(size_t(1), received.size());\r\n            Assert::AreEqual(&quot;hello&quot;, received&#x5B;0].c_str());\r\n        }\r\n\r\n        TEST_METHOD(SendTwoSubscribers)\r\n        {\r\n            MessageBus bus;\r\n            vector&lt;string&gt; received;\r\n            auto subscriber1 = &#x5B;&amp;received](MyMessage&amp; m) { received.push_back(m.get_Text() + &quot;1&quot;); };\r\n            auto subscriber2 = &#x5B;&amp;received](MyMessage&amp; m) { received.push_back(m.get_Text() + &quot;2&quot;); };\r\n\r\n            bus.Subscribe&lt;MyMessage&gt;(subscriber1);\r\n            bus.Subscribe&lt;MyMessage&gt;(subscriber2);\r\n            bus.Send(MyMessage(&quot;hello&quot;));\r\n\r\n            Assert::AreEqual(size_t(2), received.size());\r\n            Assert::AreEqual(&quot;hello1&quot;, received&#x5B;0].c_str());\r\n            Assert::AreEqual(&quot;hello2&quot;, received&#x5B;1].c_str());\r\n        }\r\n\r\n        TEST_METHOD(SendTwoOneSubscriberEach)\r\n        {\r\n            MessageBus bus;\r\n            vector&lt;string&gt; received;\r\n            auto subscriber1 = &#x5B;&amp;received](MyMessage&amp; m) { received.push_back(m.get_Text() + &quot;1&quot;); };\r\n            auto subscriber2 = &#x5B;&amp;received](MyOtherMessage&amp; m) { received.push_back(m.get_Text() + &quot;2&quot;); };\r\n\r\n            bus.Subscribe&lt;MyMessage&gt;(subscriber1);\r\n            bus.Subscribe&lt;MyOtherMessage&gt;(subscriber2);\r\n            bus.Send(MyMessage(&quot;one-hello&quot;));\r\n            bus.Send(MyOtherMessage(&quot;two-hello&quot;));\r\n\r\n            Assert::AreEqual(size_t(2), received.size());\r\n            Assert::AreEqual(&quot;one-hello1&quot;, received&#x5B;0].c_str());\r\n            Assert::AreEqual(&quot;two-hello2&quot;, received&#x5B;1].c_str());\r\n        }\r\n\r\n        TEST_METHOD(SendTwoSimpleTypesOneSubscriberEach)\r\n        {\r\n            MessageBus bus;\r\n            vector&lt;string&gt; received;\r\n            auto subscriber1 = &#x5B;&amp;received](string&amp; m) { received.push_back(string(&quot;S=&quot;) + m); };\r\n            auto subscriber2 = &#x5B;&amp;received](int&amp; m)\r\n            {\r\n                stringstream ss;\r\n                ss &lt;&lt; &quot;N=&quot; &lt;&lt; m;\r\n                received.push_back(ss.str());\r\n            };\r\n\r\n            bus.Subscribe&lt;string&gt;(subscriber1);\r\n            bus.Subscribe&lt;int&gt;(subscriber2);\r\n            bus.Send(string(&quot;xyz&quot;));\r\n            int msg = 123;\r\n            bus.Send(msg);\r\n\r\n            Assert::AreEqual(size_t(2), received.size());\r\n            Assert::AreEqual(&quot;S=xyz&quot;, received&#x5B;0].c_str());\r\n            Assert::AreEqual(&quot;N=123&quot;, received&#x5B;1].c_str());\r\n        }\r\n\r\n        TEST_METHOD(SendTwoInstancesThreeSubscribersEachBeforeAndAfter)\r\n        {\r\n            MessageBus bus1;\r\n            MessageBus bus2;\r\n            vector&lt;string&gt; received;\r\n            auto subscriber1 = &#x5B;&amp;received](string&amp; m) { received.push_back(string(&quot;S1=&quot;) + m); };\r\n            auto subscriber2 = &#x5B;&amp;received](string&amp; m) { received.push_back(string(&quot;S2=&quot;) + m); };\r\n            auto subscriber3 = &#x5B;&amp;received](string&amp; m) { received.push_back(string(&quot;S3=&quot;) + m); };\r\n            auto subscriber4 = &#x5B;&amp;received](string&amp; m) { received.push_back(string(&quot;S4=&quot;) + m); };\r\n            auto subscriber5 = &#x5B;&amp;received](int&amp; m)\r\n            {\r\n                stringstream ss;\r\n                ss &lt;&lt; &quot;S5=&quot; &lt;&lt; m;\r\n                received.push_back(ss.str());\r\n            };\r\n            auto subscriber6 = &#x5B;&amp;received](int&amp; m)\r\n            {\r\n                stringstream ss;\r\n                ss &lt;&lt; &quot;S6=&quot; &lt;&lt; m;\r\n                received.push_back(ss.str());\r\n            };\r\n\r\n            bus1.Subscribe&lt;string&gt;(subscriber1);\r\n            bus2.Subscribe&lt;string&gt;(subscriber2);\r\n            bus1.Send(string(&quot;aaa&quot;));\r\n            bus2.Send(string(&quot;bbb&quot;));\r\n            bus1.Subscribe&lt;string&gt;(subscriber3);\r\n            bus2.Subscribe&lt;string&gt;(subscriber4);\r\n            bus1.Send(string(&quot;ccc&quot;));\r\n            bus2.Send(string(&quot;ddd&quot;));\r\n            bus1.Subscribe&lt;int&gt;(subscriber5);\r\n            bus2.Subscribe&lt;int&gt;(subscriber6);\r\n            int msg = 1;\r\n            bus1.Send(msg);\r\n            msg = 2;\r\n            bus2.Send(msg);\r\n\r\n            Assert::AreEqual(size_t(8), received.size());\r\n            Assert::AreEqual(&quot;S1=aaa&quot;, received&#x5B;0].c_str());\r\n            Assert::AreEqual(&quot;S2=bbb&quot;, received&#x5B;1].c_str());\r\n            Assert::AreEqual(&quot;S1=ccc&quot;, received&#x5B;2].c_str());\r\n            Assert::AreEqual(&quot;S3=ccc&quot;, received&#x5B;3].c_str());\r\n            Assert::AreEqual(&quot;S2=ddd&quot;, received&#x5B;4].c_str());\r\n            Assert::AreEqual(&quot;S4=ddd&quot;, received&#x5B;5].c_str());\r\n            Assert::AreEqual(&quot;S5=1&quot;, received&#x5B;6].c_str());\r\n            Assert::AreEqual(&quot;S6=2&quot;, received&#x5B;7].c_str());\r\n        }\r\n\r\n        class MyMessage\r\n        {\r\n        public:\r\n            MyMessage(const string&amp; text)\r\n                : text_(text)\r\n            {\r\n            }\r\n\r\n            const string&amp; get_Text() const\r\n            {\r\n                return text_;\r\n            }\r\n\r\n        private:\r\n            string text_;\r\n        };\r\n\r\n        class MyOtherMessage : public MyMessage\r\n        {\r\n        public:\r\n            MyOtherMessage(const string&amp; text)\r\n                : MyMessage(text)\r\n            {\r\n            }\r\n        };\r\n    };\r\n}\r\n<\/pre>\n<p>The only noticeable differences here are the more verbose assertions (maybe I&#8217;ll try out <a href=\"https:\/\/github.com\/evolutional\/Chamois\">Chamois<\/a> later) and the inability to use <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/template_argument_deduction\">template argument deduction<\/a> for the <code>Subscribe<\/code> calls (probably because I&#8217;m using <a href=\"https:\/\/herbsutter.com\/2013\/08\/12\/gotw-94-solution-aaa-style-almost-always-auto\/\"><code>auto<\/code><\/a> with <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/cpp\/lambda-expressions-in-cpp?view=vs-2017\">lambdas<\/a> instead of directly assigning <a href=\"https:\/\/stackoverflow.com\/questions\/25848690\/should-i-use-stdfunction-or-a-function-pointer-in-c\"><code>std::function<\/code><\/a> instances). Now for the code:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ MessageBus.h\r\n\r\n#pragma once\r\n\r\n#include &lt;functional&gt;\r\n#include &lt;map&gt;\r\n#include &lt;vector&gt;\r\n\r\nclass MessageBus\r\n{\r\nprivate:\r\n    typedef std::function&lt;void(void*)&gt; Func;\r\n    typedef std::vector&lt;Func&gt; FuncList;\r\n    typedef void* TypeId;\r\n    typedef std::map&lt;TypeId, FuncList&gt; Map;\r\n\r\npublic:\r\n    MessageBus()\r\n        : subscribers_()\r\n    {\r\n    }\r\n\r\n    template&lt;typename TMessage&gt;\r\n    void Subscribe(std::function&lt;void(TMessage&amp;)&gt; subscriber)\r\n    {\r\n        TypeId key = Id&lt;TMessage&gt;();\r\n        FuncList&amp; list = subscribers_&#x5B;key];\r\n        Func f = &#x5B;subscriber](void* message) { subscriber(*static_cast&lt;TMessage*&gt;(message)); };\r\n        list.push_back(f);\r\n    }\r\n\r\n    template&lt;typename TMessage&gt;\r\n    void Send(TMessage&amp; message) const\r\n    {\r\n        TypeId key = Id&lt;TMessage&gt;();\r\n        auto it = subscribers_.find(key);\r\n        if (it != subscribers_.cend())\r\n        {\r\n            const FuncList&amp; list = it-&gt;second;\r\n            for (auto f : list)\r\n            {\r\n                f(&amp;message);\r\n            }\r\n        }\r\n    }\r\n\r\nprivate:\r\n    MessageBus(const MessageBus&amp;) = delete;\r\n    MessageBus&amp; operator=(const MessageBus&amp;) = delete;\r\n\r\n    template &lt;typename T&gt;\r\n    static TypeId Id()\r\n    {\r\n        static T* id = nullptr;\r\n        return &amp;id;\r\n    }\r\n\r\n    Map subscribers_;\r\n};\r\n<\/pre>\n<p>Since <a href=\"https:\/\/stackoverflow.com\/questions\/14303327\/multicast-delegates-in-c\">C++ doesn&#8217;t have multicast delegates<\/a>, we are using a <code>std::vector<\/code> of <code>std::function<\/code>s. You can also see the template trick I alluded to above to get a consistent ID for each message type, adapted from <a href=\"https:\/\/stackoverflow.com\/users\/1576384\/araud\">araud<\/a>&#8216;s <a href=\"https:\/\/stackoverflow.com\/questions\/8001207\/compile-time-typeid-without-rtti-with-gcc\/23348670#23348670\">StackOverflow answer<\/a>. Otherwise, this is fairly close in spirit to the C# version, including the &#8220;System.Object&#8221; analog via the lambda with <code>void*<\/code> pointer cast.<\/p>\n<p>So, obviously, the C++ implementation is going to be faster, due to the lack of reflection, right? Let&#8217;s write a simple benchmark to find out:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;chrono&gt;\r\n#include &lt;iostream&gt;\r\n\r\n#include &quot;MessageBus.h&quot;\r\n\r\nusing namespace std;\r\n\r\nvoid Benchmark(int iterations)\r\n{\r\n    size_t count = 0;\r\n    auto subscriber = &#x5B;&amp;count](const wstring&amp; m) { count += m.length(); };\r\n    MessageBus bus;\r\n\r\n    bus.Subscribe&lt;wstring&gt;(subscriber);\r\n\r\n    auto func = &#x5B;&amp;bus](int n)\r\n    {\r\n        for (int i = 0; i &lt; n; ++i)\r\n        {\r\n            for (wchar_t c = L'A'; c &lt;= L'Z'; ++c)\r\n            {\r\n                bus.Send(wstring(1, c));\r\n            }\r\n        }\r\n    };\r\n\r\n    chrono::high_resolution_clock clock;\r\n    auto start = clock.now();\r\n\r\n    func(iterations);\r\n\r\n    auto end = clock.now();\r\n    auto duration = end - start;\r\n    double nsecPerOp = static_cast&lt;double&gt;(duration.count()) \/ iterations;\r\n\r\n    wcout &lt;&lt; L&quot;Average operation time: &quot; &lt;&lt; nsecPerOp &lt;&lt; L&quot; ns (var count = &quot; &lt;&lt; count &lt;&lt; L&quot;)&quot; &lt;&lt; endl;\r\n}\r\n\r\nint main()\r\n{\r\n    Benchmark(1);\r\n    Benchmark(16);\r\n    Benchmark(256);\r\n    Benchmark(4096);\r\n    Benchmark(65536);\r\n    Benchmark(262144);\r\n    Benchmark(524288);\r\n    Benchmark(524288);\r\n    Benchmark(524288);\r\n    Benchmark(524288);\r\n    Benchmark(524288);\r\n\r\n    return 0;\r\n}\r\n<\/pre>\n<p>Here is the output on my system:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nAverage operation time: 2171 ns (var count = 26)\r\nAverage operation time: 1880.13 ns (var count = 416)\r\nAverage operation time: 1848.61 ns (var count = 6656)\r\nAverage operation time: 1884.88 ns (var count = 106496)\r\nAverage operation time: 1938 ns (var count = 1703936)\r\nAverage operation time: 1867.31 ns (var count = 6815744)\r\nAverage operation time: 1871.75 ns (var count = 13631488)\r\nAverage operation time: 1863.55 ns (var count = 13631488)\r\nAverage operation time: 1851.69 ns (var count = 13631488)\r\nAverage operation time: 1854.21 ns (var count = 13631488)\r\nAverage operation time: 1854.9 ns (var count = 13631488)\r\n<\/pre>\n<p>Let&#8217;s call this 1860 ns per operation. Now for the same benchmark, translated into C#:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    using System;\r\n    using System.Diagnostics;\r\n\r\n    internal static class Program\r\n    {\r\n        private static void Benchmark(int iterations)\r\n        {\r\n            ulong count = 0;\r\n            Action&lt;string&gt; subscriber = m =&gt; count += (uint)m.Length;\r\n            MessageBus bus = new MessageBus();\r\n\r\n            bus.Subscribe(subscriber);\r\n\r\n            Action&lt;int&gt; func = n =&gt;\r\n            {\r\n                for (int i = 0; i &lt; n; ++i)\r\n                {\r\n                    for (char c = 'A'; c &lt;= 'Z'; ++c)\r\n                    {\r\n                        string msg = new string(c, 1);\r\n                        bus.Send(msg);\r\n                    }\r\n                }\r\n            };\r\n\r\n            Stopwatch clock = Stopwatch.StartNew();\r\n\r\n            func(iterations);\r\n\r\n            TimeSpan duration = clock.Elapsed;\r\n            double nsecPerOp = (duration.Ticks * 100.0) \/ iterations;\r\n\r\n            Console.WriteLine($&quot;Average operation time: {nsecPerOp} ns (var count = {count})&quot;);\r\n        }\r\n\r\n        private static void Main(string&#x5B;] args)\r\n        {\r\n            Benchmark(1);\r\n            Benchmark(16);\r\n            Benchmark(256);\r\n            Benchmark(4096);\r\n            Benchmark(65536);\r\n            Benchmark(262144);\r\n            Benchmark(524288);\r\n            Benchmark(524288);\r\n            Benchmark(524288);\r\n            Benchmark(524288);\r\n            Benchmark(524288);\r\n        }\r\n    }\r\n<\/pre>\n<p>The output on my system:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nAverage operation time: 1030200 ns (var count = 26)\r\nAverage operation time: 1550 ns (var count = 416)\r\nAverage operation time: 1550.390625 ns (var count = 6656)\r\nAverage operation time: 1625.09765625 ns (var count = 106496)\r\nAverage operation time: 1131.09283447266 ns (var count = 1703936)\r\nAverage operation time: 1075.38948059082 ns (var count = 6815744)\r\nAverage operation time: 1042.32959747314 ns (var count = 13631488)\r\nAverage operation time: 1047.92461395264 ns (var count = 13631488)\r\nAverage operation time: 1191.17279052734 ns (var count = 13631488)\r\nAverage operation time: 1095.16143798828 ns (var count = 13631488)\r\nAverage operation time: 1215.71636199951 ns (var count = 13631488)\r\n<\/pre>\n<p>Aside from some rather excessive warmup time, the results here are quite a bit faster at ~1100 ns per operation. How can this be?!<\/p>\n<p>It turns out I have a small mistake in my C++ implementation. Refer back to the <code>Send<\/code> method:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n    template&lt;typename TMessage&gt;\r\n    void Send(const TMessage&amp; message) const\r\n    {\r\n        TypeId key = Id&lt;TMessage&gt;();\r\n        auto it = subscribers_.find(key);\r\n        if (it != subscribers_.cend())\r\n        {\r\n            const FuncList&amp; list = it-&gt;second;\r\n            for (auto f : list)\r\n            {\r\n                f(&amp;message);\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>Do you see the issue? If not, know that <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/cpp\/auto-cpp?view=vs-2017#references-and-cv-qualifiers\">auto drops reference qualifiers<\/a>. I should have written <code>auto& f<\/code>; as it is, the function is copied to a new local variable instead of being invoked from the reference. Making that change and rerunning the benchmark gives the following output:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nAverage operation time: 310 ns (var count = 26)\r\nAverage operation time: 174.438 ns (var count = 416)\r\nAverage operation time: 167.176 ns (var count = 6656)\r\nAverage operation time: 156.878 ns (var count = 106496)\r\nAverage operation time: 154.034 ns (var count = 1703936)\r\nAverage operation time: 151.468 ns (var count = 6815744)\r\nAverage operation time: 154.744 ns (var count = 13631488)\r\nAverage operation time: 151.567 ns (var count = 13631488)\r\nAverage operation time: 158.178 ns (var count = 13631488)\r\nAverage operation time: 150.577 ns (var count = 13631488)\r\nAverage operation time: 150.845 ns (var count = 13631488)\r\n<\/pre>\n<p>There we go &#8212; better than 10X performance improvement.<\/p>\n<p>Now, if only C# had a workable compile-time type information system. (Unfortunately, <a href=\"https:\/\/stackoverflow.com\/questions\/29878137\/nameof-with-generics\">nameof doesn&#8217;t cut it<\/a>.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The message bus is a typical pattern to allow loosely coupled software components to communicate, usually in an event-driven manner. Don&#8217;t let the enterprise-y description deter you &#8212; a simple message bus for a single process scenario can be quite&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[91,101,104],"tags":[],"class_list":["post-5586","post","type-post","status-publish","format-standard","hentry","category-design","category-native","category-performance"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5586","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5586"}],"version-history":[{"count":6,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5586\/revisions"}],"predecessor-version":[{"id":5592,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5586\/revisions\/5592"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5586"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5586"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5586"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}