{"id":5548,"date":"2018-10-28T13:00:33","date_gmt":"2018-10-28T13:00:33","guid":{"rendered":"http:\/\/writeasync.net\/?p=5548"},"modified":"2018-10-27T19:18:26","modified_gmt":"2018-10-27T19:18:26","slug":"operation-throttle-part-2","status":"publish","type":"post","link":"https:\/\/writeasync.net\/?p=5548","title":{"rendered":"Operation throttle: part 2"},"content":{"rendered":"<p>Last time, I introduced the topic of <a href=\"http:\/\/writeasync.net\/?p=5542\">fixed operation rates<\/a> and how one might implement this functionality with ad-hoc code. Today, I will generalize the concept with a simple <code>Throttle<\/code> library.<\/p>\n<p>As I mentioned before, we have to keep track of two time quantities if we want a properly functioning throttle &#8212; the time spent for the operation itself and the wait time. Let&#8217;s explore a few concrete scenarios to get a feel for how this would look.<\/p>\n<h2>Low latency, high rate<\/h2>\n<p>Imagine we want to perform 2000 operations per second, and each operation completes in 1\/10 of a millisecond. If the operation took zero time to complete, the wait time would be 1\/2000 seconds = 0.5 ms. Accounting for the true latency (0.1 ms), we can subtract the two and come up with an actual delay of 0.4 ms per operation. However, as we saw before, waiting for too small a time results in too much inaccuracy. So we will want to batch these delay times after sufficient accumulation. If our minimum wait time is 10 ms, we would have to perform 10\/.4 = 25 operations before we would need to wait that long. If we were to graph this on a time chart it would look like so:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n@ = op, z = sleep\r\n\r\n|    @@@@@@@@@@@@@@@@@@@@@@@@@zzzzzzzz \/ zzzzzzz@@@@@@@  \r\n|             :         :         :    \/  :         :    \r\n| T |----:----:----:----:----:----:--- \/ -:----:----:--  . . . -&gt;\r\n|(ms)   0.5  1.0  1.5  2.0  2.5  3.0    12.0 12.5 13.0\r\n<\/pre>\n<p>The pattern would repeat like this, giving us an effective rate of 25 ops per 12.5 ms = 2000 ops\/sec, as intended.<\/p>\n<h2>High latency, low rate<\/h2>\n<p>Let&#8217;s slow things down immensely and imagine a scenario of a two second long operation to be performed 10 times per minute. Using similar calculations as above, we can plan to delay for four seconds after every operation. With such a long delay, the amount of error introduced should be very low, so no batching is necessary.<\/p>\n<h2>Low latency, low rate<\/h2>\n<p>Imagine the previous scenario but with an operation that only takes 1\/10 of a second. We would simply delay for 5.9 seconds instead of 4.0.<\/p>\n<h2>High latency, high rate<\/h2>\n<p>This is more of a degenerate case. We have an operation that we would like to perform, say, 100 times per second, but the operation itself takes 100 ms. The best rate we could ever achieve is 1 op\/100 ms = 10 ops\/sec. So in this case, we would keep missing our target rate and therefore never actually delay.<\/p>\n<p>The above scenarios are rather idealized in that the waiting time is perfectly accurate and all operations have uniform latency. In reality this is unlikely. Let&#8217;s look at the first scenario again through a more realistic lens.<\/p>\n<h2>Low latency, high rate with jitter<\/h2>\n<p>Let&#8217;s imagine the 2000 ops\/sec scenario again but with alternating operation latency of 0.1 and 0.2 ms and an observed delay that alternates between -0.2 ms and +0.1 ms on every attempt to sleep. Given the alternating latency, we would have a corresponding alternating delay of 0.4 and 0.3 ms, respectively &#8212; or 0.7 ms every two operations. After 14 operations, we&#8217;d accumulate 4.9 ms of delay, and 9.8 ms of delay after 28 operations. After operation 29, we would have accumulated 10.2 ms of delay, so we would sleep for that long. However, we would only sleep 10.0 ms in reality (-0.2 ms), putting us 0.2 ms early for the next operation. This means 0.2 ms of extra delay to carry forward into the next delay calculations. Since we ended at operation 29 before, our latency pattern starting at operation 30 is 0.2 and 0.1 ms with delay of 0.3 and 0.4 ms. As before, after 28 operations, we would have accumulated 9.8 ms of delay. With the extra 0.2 ms carried forward, we would now have exactly enough to sleep for 10 ms. This time our sleep ends 0.1 ms late. Thus, we would carry forward a <em>negative<\/em> delay and continue our calculations in a similar way as before.<\/p>\n<p>I admit, the above prose makes the situation seem very complicated. But the implementation is actually not terribly difficult. Writing the correct tests with proper expected results is really the hardest part, and we have basically set them up in English above. So let&#8217;s get coding!<\/p>\n<p>First, we need a <code>Rate<\/code> struct to express the operation rate. Starting with the tests:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    &#x5B;TestClass]\r\n    public class RateTest\r\n    {\r\n        &#x5B;TestMethod]\r\n        public void PerSecondTest()\r\n        {\r\n            Rate.PerSecond(0.5).Time.Should().Be(TimeSpan.FromSeconds(2.0d));\r\n            Rate.PerSecond(1).Time.Should().Be(TimeSpan.FromSeconds(1.0d));\r\n            Rate.PerSecond(2).Time.Should().Be(TimeSpan.FromSeconds(0.5d));\r\n            Rate.PerSecond(100).Time.Should().Be(TimeSpan.FromSeconds(0.01d));\r\n            Rate.PerSecond(-1).Time.Should().Be(TimeSpan.MaxValue);\r\n            Rate.PerSecond(0).Time.Should().Be(TimeSpan.MaxValue);\r\n            Rate.PerSecond(1000000000).Time.Should().Be(TimeSpan.Zero);\r\n        }\r\n\r\n        &#x5B;TestMethod]\r\n        public void PerMinuteTest()\r\n        {\r\n            Rate.PerMinute(0.5).Time.Should().Be(TimeSpan.FromSeconds(120.0d));\r\n            Rate.PerMinute(1).Time.Should().Be(TimeSpan.FromSeconds(60.0d));\r\n            Rate.PerMinute(2).Time.Should().Be(TimeSpan.FromSeconds(30.0d));\r\n            Rate.PerMinute(120).Time.Should().Be(TimeSpan.FromSeconds(0.5d));\r\n            Rate.PerMinute(-1).Time.Should().Be(TimeSpan.MaxValue);\r\n            Rate.PerMinute(0).Time.Should().Be(TimeSpan.MaxValue);\r\n            Rate.PerMinute(1000000000).Time.Should().Be(TimeSpan.Zero);\r\n        }\r\n    }\r\n<\/pre>\n<p>This implementation passes the tests, including the given boundary conditions (negative, zero, and too large):<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public struct Rate\r\n    {\r\n        private readonly TimeSpan time;\r\n\r\n        private Rate(TimeSpan time)\r\n        {\r\n            this.time = time;\r\n        }\r\n\r\n        public TimeSpan Time =&gt; this.time;\r\n\r\n        public static Rate PerSecond(double ops)\r\n        {\r\n            long ticks = (long)(10000000 \/ ops);\r\n            if (ticks &lt; 0)\r\n            {\r\n                ticks = long.MaxValue;\r\n            }\r\n\r\n            return new Rate(TimeSpan.FromTicks(ticks));\r\n        }\r\n\r\n        public static Rate PerMinute(double ops)\r\n        {\r\n            return PerSecond(ops \/ 60);\r\n        }\r\n    }\r\n<\/pre>\n<p>Since the <code>Throttle<\/code> needs a time reference and a method to initiate a delay, we need a clock abstraction. Let&#8217;s start with that:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public interface IClock\r\n    {\r\n        \/\/ Gets the elapsed time so far.\r\n        TimeSpan Elapsed { get; }\r\n\r\n        \/\/ Delays execution for the given interval.\r\n        void Delay(TimeSpan interval);\r\n    }\r\n<\/pre>\n<p>The real version of the clock looks like this:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public sealed class RealClock : IClock\r\n    {\r\n        private readonly Stopwatch stopwatch;\r\n\r\n        public RealClock()\r\n        {\r\n            this.stopwatch = Stopwatch.StartNew();\r\n        }\r\n\r\n        public TimeSpan Elapsed =&gt; this.stopwatch.Elapsed;\r\n\r\n        public void Delay(TimeSpan interval) =&gt; Thread.Sleep(interval);\r\n    }\r\n<\/pre>\n<p>We also need a fake version for testing purposes:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    internal sealed class FakeClock : IClock\r\n    {\r\n        private readonly Func&lt;TimeSpan, TimeSpan&gt; onDelay;\r\n\r\n        public FakeClock(Func&lt;TimeSpan, TimeSpan&gt; onDelay)\r\n        {\r\n            this.onDelay = onDelay;\r\n        }\r\n\r\n        public TimeSpan Elapsed { get; set; }\r\n\r\n        public void Delay(TimeSpan interval)\r\n        {\r\n            this.Elapsed += this.onDelay(interval);\r\n        }\r\n    }\r\n<\/pre>\n<p>The fake version allows us to set the elapsed time explicitly. It also provides a callback on every delay to determine how much time should be added to the clock (useful once we get to the &#8220;jitter&#8221; scenario).<\/p>\n<p>Now to implement the first test scenario above:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    &#x5B;TestClass]\r\n    public class ThrottleTest\r\n    {\r\n        &#x5B;TestMethod]\r\n        public void LowLatencyHighRate()\r\n        {\r\n            StringBuilder log = new StringBuilder();\r\n            TimeSpan latency = MS(0.1d);\r\n            Func&lt;TimeSpan, TimeSpan&gt; callback = (TimeSpan t) =&gt;\r\n            {\r\n                log.AppendFormat(&quot;&#x5B;z*{0}]&quot;, t.TotalMilliseconds);\r\n                return t;\r\n            };\r\n            FakeClock clock = new FakeClock(callback);\r\n            Throttle throttle = new Throttle(Rate.PerSecond(2000.0d), clock);\r\n\r\n            for (int i = 0; i &lt; 60; ++i)\r\n            {\r\n                clock.Elapsed += latency;\r\n                log.Append(&quot;@&quot;);\r\n                throttle.Wait();\r\n            }\r\n\r\n            log.ToString().Should().Be(\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@&#x5B;z*10]&quot; +\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@&#x5B;z*10]&quot; +\r\n                &quot;@@@@@@@@@@&quot;);\r\n            clock.Elapsed.Should().Be(MS(26.0d));\r\n        }\r\n\r\n        private static TimeSpan MS(double ms) =&gt; TimeSpan.FromTicks((long)(ms * 10000));\r\n    }\r\n<\/pre>\n<p>Note the use of the <code>MS<\/code> helper method for dealing with fractional milliseconds. This is necessary because of the tricky behavior of <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.timespan.frommilliseconds?view=netframework-4.7.2#remarks\"><code>TimeSpan.FromMilliseconds<\/code><\/a>:<\/p>\n<blockquote><p>\nThe <code>value<\/code> parameter is converted to ticks, and that number of ticks is used to initialize the new TimeSpan. Therefore, <code>value<\/code> will only be considered accurate to the nearest millisecond.\n<\/p><\/blockquote>\n<p>This implementation is sufficient to pass this test (and indeed, any test, as we will see later):<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public sealed class Throttle\r\n    {\r\n        private static readonly TimeSpan MinimumDelay = TimeSpan.FromMilliseconds(10.0d);\r\n\r\n        private readonly TimeSpan delay;\r\n        private readonly IClock clock;\r\n\r\n        private TimeSpan mark1;\r\n        private TimeSpan nextDelay;\r\n\r\n        public Throttle(Rate rate, IClock clock)\r\n        {\r\n            this.delay = rate.Time;\r\n            this.clock = clock;\r\n        }\r\n\r\n        public void Wait()\r\n        {\r\n            TimeSpan mark2 = this.clock.Elapsed;\r\n            this.nextDelay += this.delay - (mark2 - this.mark1);\r\n            if (this.nextDelay &gt;= MinimumDelay)\r\n            {\r\n                this.clock.Delay(this.nextDelay);\r\n                this.mark1 = this.clock.Elapsed;\r\n                this.nextDelay -= this.mark1 - mark2;\r\n            }\r\n            else\r\n            {\r\n                this.mark1 = this.clock.Elapsed;\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>Next test:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        &#x5B;TestMethod]\r\n        public void HighLatencyLowRate()\r\n        {\r\n            StringBuilder log = new StringBuilder();\r\n            TimeSpan latency = MS(2000.0d);\r\n            Func&lt;TimeSpan, TimeSpan&gt; callback = (TimeSpan t) =&gt;\r\n            {\r\n                log.AppendFormat(&quot;&#x5B;z*{0}]&quot;, t.TotalMilliseconds);\r\n                return t;\r\n            };\r\n            FakeClock clock = new FakeClock(callback);\r\n            Throttle throttle = new Throttle(Rate.PerMinute(10.0d), clock);\r\n\r\n            for (int i = 0; i &lt; 10; ++i)\r\n            {\r\n                clock.Elapsed += latency;\r\n                log.Append(&quot;@&quot;);\r\n                throttle.Wait();\r\n            }\r\n\r\n            log.ToString().Should().Be(\r\n                &quot;@&#x5B;z*4000]@&#x5B;z*4000]@&#x5B;z*4000]@&#x5B;z*4000]@&#x5B;z*4000]&quot; +\r\n                &quot;@&#x5B;z*4000]@&#x5B;z*4000]@&#x5B;z*4000]@&#x5B;z*4000]@&#x5B;z*4000]&quot;);\r\n            clock.Elapsed.Should().Be(MS(60000.0d));\r\n        }\r\n<\/pre>\n<p>This one passes right out of the gate. How about this next test?<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        &#x5B;TestMethod]\r\n        public void LowLatencyLowRate()\r\n        {\r\n            StringBuilder log = new StringBuilder();\r\n            TimeSpan latency = MS(100.0d);\r\n            Func&lt;TimeSpan, TimeSpan&gt; callback = (TimeSpan t) =&gt;\r\n            {\r\n                log.AppendFormat(&quot;&#x5B;z*{0}]&quot;, t.TotalMilliseconds);\r\n                return t;\r\n            };\r\n            FakeClock clock = new FakeClock(callback);\r\n            Throttle throttle = new Throttle(Rate.PerMinute(10.0d), clock);\r\n\r\n            for (int i = 0; i &lt; 10; ++i)\r\n            {\r\n                clock.Elapsed += latency;\r\n                log.Append(&quot;@&quot;);\r\n                throttle.Wait();\r\n            }\r\n\r\n            log.ToString().Should().Be(\r\n                &quot;@&#x5B;z*5900]@&#x5B;z*5900]@&#x5B;z*5900]@&#x5B;z*5900]@&#x5B;z*5900]&quot; +\r\n                &quot;@&#x5B;z*5900]@&#x5B;z*5900]@&#x5B;z*5900]@&#x5B;z*5900]@&#x5B;z*5900]&quot;);\r\n            clock.Elapsed.Should().Be(MS(60000.0d));\r\n        }\r\n<\/pre>\n<p>It passes as well. Now for the degenerate case:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        &#x5B;TestMethod]\r\n        public void HighLatencyHighRate()\r\n        {\r\n            StringBuilder log = new StringBuilder();\r\n            TimeSpan latency = MS(100.0d);\r\n            Func&lt;TimeSpan, TimeSpan&gt; callback = (TimeSpan t) =&gt;\r\n            {\r\n                log.AppendFormat(&quot;&#x5B;z*{0}]&quot;, t.TotalMilliseconds);\r\n                return t;\r\n            };\r\n            FakeClock clock = new FakeClock(callback);\r\n            Throttle throttle = new Throttle(Rate.PerSecond(100.0d), clock);\r\n\r\n            for (int i = 0; i &lt; 75; ++i)\r\n            {\r\n                clock.Elapsed += latency;\r\n                log.Append(&quot;@&quot;);\r\n                throttle.Wait();\r\n            }\r\n\r\n            log.ToString().Should().Be(\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@&quot; +\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@&quot; +\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@&quot;);\r\n            clock.Elapsed.Should().Be(MS(7500.0d));\r\n        }\r\n<\/pre>\n<p>Still passes! Now for the more complicated jitter scenario:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        &#x5B;TestMethod]\r\n        public void LowLatencyHighRateWithJitter()\r\n        {\r\n            StringBuilder log = new StringBuilder();\r\n            int delayCount = 0;\r\n            Func&lt;TimeSpan, TimeSpan&gt; callback = (TimeSpan t) =&gt;\r\n            {\r\n                \/\/ Clock jitter\r\n                t += (delayCount++ % 2 == 0) ? MS(-0.2d) : MS(0.1d);\r\n                log.AppendFormat(&quot;&#x5B;z*{0}]&quot;, t.TotalMilliseconds);\r\n                return t;\r\n            };\r\n            FakeClock clock = new FakeClock(callback);\r\n            Throttle throttle = new Throttle(Rate.PerSecond(2000.0d), clock);\r\n\r\n            for (int i = 0; i &lt; 100; ++i)\r\n            {\r\n                \/\/ Operation jitter\r\n                TimeSpan latency = (i % 2 == 0) ? MS(0.1d) : MS(0.2d);\r\n                clock.Elapsed += latency;\r\n                log.Append(&quot;@&quot;);\r\n                throttle.Wait();\r\n            }\r\n\r\n            log.ToString().Should().Be(\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&#x5B;z*10]&quot; +\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@@@@&#x5B;z*10.1]&quot; +\r\n                &quot;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&#x5B;z*9.8]&quot; +\r\n                &quot;@@@@@@@@@@@@@@&quot;);\r\n            clock.Elapsed.Should().Be(\r\n                MS(15.0d) + \/\/ total operation time = (0.1 + 0.2) * 50\r\n                MS(10.0d + 10.1d + 9.8d)); \/\/ total wait time\r\n        }\r\n<\/pre>\n<p>This one also works. At this point, we should be confident in our basic implementation.<\/p>\n<p>For good measure, let&#8217;s go back to our original benchmark and replace the <code>Thread.Sleep<\/code> with <code>Throttle.Wait<\/code>. Since <code>Throttle<\/code> is stateful, we have to modify our benchmark slightly to <a href=\"https:\/\/benchmarkdotnet.org\/articles\/faq.html\">avoid side-effects<\/a>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class ThrottlePerf\r\n{\r\n    private UdpClient client;\r\n \r\n    &#x5B;Params(2000)]\r\n    public int R; \/\/ rate per second\r\n\r\n    &#x5B;Params(1000)]\r\n    public int N;\r\n \r\n    &#x5B;GlobalSetup]\r\n    public void Setup()\r\n    {\r\n        this.client = new UdpClient(&quot;localhost&quot;, 17);\r\n    }\r\n \r\n    &#x5B;Benchmark]\r\n    public void Run()\r\n    {\r\n        Throttle throttle = new Throttle(Rate.PerSecond(this.R), new RealClock());\r\n        for (int i = 0; i &lt; this.N; ++i)\r\n        {\r\n            byte&#x5B;] request = Encoding.ASCII.GetBytes(&quot;QOTD&quot;);\r\n            this.client.Send(request, request.Length);\r\n            throttle.Wait();\r\n        }\r\n    }\r\n \r\n    &#x5B;GlobalCleanup]\r\n    public void Cleanup()\r\n    {\r\n        this.client.Close();\r\n    }\r\n}\r\n<\/pre>\n<p>The results are pretty close, only about 1% faster than the expected rate:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nMethod |    R |    N |     Mean |     Error |    StdDev |\r\n------ |----- |----- |---------:|----------:|----------:|\r\n   Run | 2000 | 1000 | 494.6 ms | 0.2404 ms | 0.2131 ms |\r\n<\/pre>\n<p>Well, that&#8217;s more than enough for now. I will leave the async version as an exercise to the reader.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last time, I introduced the topic of fixed operation rates and how one might implement this functionality with ad-hoc code. Today, I will generalize the concept with a simple Throttle library. As I mentioned before, we have to keep track&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,104,51],"tags":[],"class_list":["post-5548","post","type-post","status-publish","format-standard","hentry","category-design","category-performance","category-testing"],"_links":{"self":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5548","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5548"}],"version-history":[{"count":6,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5548\/revisions"}],"predecessor-version":[{"id":5554,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5548\/revisions\/5554"}],"wp:attachment":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5548"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5548"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}