{"id":36051,"date":"2012-12-28T09:56:08","date_gmt":"2012-12-28T09:56:08","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/andrewarnottms\/2012\/12\/28\/the-cost-of-context-switches\/"},"modified":"2019-04-03T21:34:17","modified_gmt":"2019-04-04T04:34:17","slug":"the-cost-of-context-switches","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/the-cost-of-context-switches\/","title":{"rendered":"The cost of context switches"},"content":{"rendered":"<p>Context switches are not free. But how expensive are they? I wrote a small program to find out, and I\u2019m sharing the program and its results here.<\/p>\n<p>I focused on purely context switches (no work is actually performed between context switches). So it\u2019s not a real-world scenario, but it really brings out the hidden costs. Below are the results 500,000 context switches performing no work between each one.<\/p>\n<pre>Executing 500000 work cycles of 0 iterations each, in different ways...\r\nScenario            Total time (ms)     Time per unit (\u00b5s)\r\nNo-switch           0                   0.0002\r\nAsync w\/o yield     67                  0.0353\r\nAsync w\/ yield      664                 0.349\r\nThread switches     5215                2.7412<\/pre>\n<p>Notice how with each kind, the order of magnitude of the overhead increases. The code below will help you understand what each each scenario name actually means. Then we add a bit of work (counting to 500) per context switch, which is closer to a possible real-world work load (although relatively lightweight) that might occur for a given context:<\/p>\n<pre>Executing 500000 work cycles of 500 iterations each, in different ways...\r\nScenario            Total time (ms)     Time per unit (\u00b5s)\r\nNo-switch           380                 0.1998\r\nAsync w\/o yield     368                 0.1935\r\nAsync w\/ yield      832                 0.4374\r\nThread switches     5185                2.7257<\/pre>\n<p>Suddenly no context switch and async methods all share an order of magnitude, while thread switches still takes significantly longer. In fact closely comparing shows that Async w\/o yield is faster than no switch at all. This of course is ludicrous and can be written off as noise. But several runs produced the same result, so we can glean from this that when doing even a small amount of work per context switch, that the no-yield async method adds insignificant overhead.<\/p>\n<p>Following is the application that produced the above results.<\/p>\n<pre class=\"prettyprint\">using System;\r\nusing System.Diagnostics;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\n\r\nclass Program {\r\n\tconst int unitSize = 500;\r\n\tconst int workSize = 500000;\r\n\tconst string spacing = &quot;{0,-20}{1,-20}{2,-20}&quot;;\r\n\r\n\tprivate static void Main(string[] args) {\r\n\t\tConsole.WriteLine(&quot;Executing {0} work cycles of {1} iterations each, in different ways...&quot;, workSize, unitSize);\r\n\r\n\t\tConsole.WriteLine(spacing, &quot;Scenario&quot;, &quot;Total time (ms)&quot;, &quot;Time per unit (\u03bcs)&quot;);\r\n\t\tScenario(&quot;No-switch&quot;, DoSync);\r\n\t\tScenario(&quot;Async w\/o yield&quot;, DoAsyncNoYield);\r\n\t\tScenario(&quot;Async w\/ yield&quot;, DoAsyncWithYield);\r\n\t\tScenario(&quot;Thread switches&quot;, ThreadSwitch);\r\n\t}\r\n\r\n\tstatic void Scenario(string name, Action operation) {\r\n\t\tGC.Collect();\r\n\t\toperation(); \/\/ warm it up\r\n\t\tvar timer = Stopwatch.StartNew();\r\n\t\toperation();\r\n\t\ttimer.Stop();\r\n\t\tConsole.WriteLine(spacing, name, timer.ElapsedMilliseconds, MicroSecondsPerItem(timer));\r\n\t}\r\n\r\n\tstatic void ThreadSwitch() {\r\n\t\tint workRemaining = workSize;\r\n\t\tvar evt = new AutoResetEvent(true);\r\n\t\tThreadStart worker = () =&gt; {\r\n\t\t\twhile (workRemaining &gt; 0) {\r\n\t\t\t\tevt.WaitOne();\r\n\t\t\t\tworkRemaining--;\r\n\t\t\t\tWorkUnit();\r\n\t\t\t\tevt.Set();\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tvar threads = new Thread[Environment.ProcessorCount];\r\n\t\tfor (int i = 0; i &lt; threads.Length; i++) {\r\n\t\t\tthreads[i] = new Thread(worker);\r\n\t\t\tthreads[i].Start();\r\n\t\t}\r\n\r\n\t\tfor (int i = 0; i &lt; threads.Length; i++) {\r\n\t\t\tthreads[i].Join();\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void DoAsyncNoYield() {\r\n\t\tvar tcs = new TaskCompletionSource&lt;object&gt;();\r\n\t\ttcs.SetResult(null);\r\n\t\tvar task = tcs.Task;\r\n\t\tTask.Run(\r\n\t\t\tasync delegate {\r\n\t\t\t\tint workRemaining = workSize;\r\n\t\t\t\twhile (--workRemaining &gt;= 0) {\r\n\t\t\t\t\tawait NoYieldHelper(task);\r\n\t\t\t\t}\r\n\t\t\t}).Wait();\r\n\t}\r\n\r\n\tstatic async Task NoYieldHelper(Task task) {\r\n\t\tWorkUnit();\r\n\t\tawait task;\r\n\t}\r\n\r\n\tstatic void DoAsyncWithYield() {\r\n\t\tTask.Run(\r\n\t\t\tasync delegate {\r\n\t\t\t\tint workRemaining = workSize;\r\n\t\t\t\twhile (--workRemaining &gt;= 0) {\r\n\t\t\t\t\tWorkUnit();\r\n\t\t\t\t\tawait Task.Yield();\r\n\t\t\t\t}\r\n\t\t\t}).Wait();\r\n\t}\r\n\r\n\tstatic void DoSync() {\r\n\t\tint workRemaining = workSize;\r\n\t\twhile (--workRemaining &gt;= 0) {\r\n\t\t\tWorkUnit();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static double MicroSecondsPerItem(Stopwatch timer) {\r\n\t\tvar ticksPerItem = (double)timer.ElapsedTicks \/ workSize;\r\n\t\tvar microSecondsPerItem = TimeSpan.FromTicks((long)(ticksPerItem * 1000)).TotalMilliseconds;\r\n\t\treturn microSecondsPerItem;\r\n\t}\r\n\r\n\tstatic void WorkUnit() {\r\n\t\tfor (int i = 0; i &lt; unitSize; i++) {\r\n\t\t}\r\n\t}\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Context switches are not free. But how expensive are they? I wrote a small program to find out, and I\u2019m sharing the program and its results here. I focused on purely context switches (no work is actually performed between context switches). So it\u2019s not a real-world scenario, but it really brings out the hidden costs. [&hellip;]<\/p>\n","protected":false},"author":2685,"featured_media":37840,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[106,4617,3914],"class_list":["post-36051","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-permierdev","tag-net","tag-andarno","tag-async"],"acf":[],"blog_post_summary":"<p>Context switches are not free. But how expensive are they? I wrote a small program to find out, and I\u2019m sharing the program and its results here. I focused on purely context switches (no work is actually performed between context switches). So it\u2019s not a real-world scenario, but it really brings out the hidden costs. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36051","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/2685"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=36051"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36051\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=36051"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=36051"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=36051"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}