Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the Managed heap not scalable to multi-core systems

Tags:

c#

.net

I was seeing some strange behavior in a multi threading application which I wrote and which was not scaling well across multiple cores.

The following code illustrates the behavior I am seeing. It appears the heap intensive operations do not scale across multiple cores rather they seem to slow down. ie using a single thread would be faster.

class Program
{
   public static Data _threadOneData = new Data();
   public static Data _threadTwoData = new Data();
   public static Data _threadThreeData = new Data();
   public static Data _threadFourData = new Data();

   static void Main(string[] args)
   {
      // Do heap intensive tests
      var start = DateTime.Now;
      RunOneThread(WorkerUsingHeap);
      var finish = DateTime.Now;
      var timeLapse = finish - start;
      Console.WriteLine("One thread using heap: " + timeLapse);

      start = DateTime.Now;
      RunFourThreads(WorkerUsingHeap);
      finish = DateTime.Now;
      timeLapse = finish - start;
      Console.WriteLine("Four threads using heap: " + timeLapse);

      // Do stack intensive tests
      start = DateTime.Now;
      RunOneThread(WorkerUsingStack);
      finish = DateTime.Now;
      timeLapse = finish - start;
      Console.WriteLine("One thread using stack: " + timeLapse);

      start = DateTime.Now;
      RunFourThreads(WorkerUsingStack);
      finish = DateTime.Now;
      timeLapse = finish - start;
      Console.WriteLine("Four threads using stack: " + timeLapse);

      Console.ReadLine();
   }

   public static void RunOneThread(ParameterizedThreadStart worker)
   {
      var threadOne = new Thread(worker);
      threadOne.Start(_threadOneData);

      threadOne.Join();
   }

   public static void RunFourThreads(ParameterizedThreadStart worker)
   {
      var threadOne = new Thread(worker);
      threadOne.Start(_threadOneData);

      var threadTwo = new Thread(worker);
      threadTwo.Start(_threadTwoData);

      var threadThree = new Thread(worker);
      threadThree.Start(_threadThreeData);

      var threadFour = new Thread(worker);
      threadFour.Start(_threadFourData);

      threadOne.Join();
      threadTwo.Join();
      threadThree.Join();
      threadFour.Join();
   }

   static void WorkerUsingHeap(object state)
   {
      var data = state as Data;
      for (int count = 0; count < 100000000; count++)
      {
         var property = data.Property;
         data.Property = property + 1;
      }
   }

   static void WorkerUsingStack(object state)
   {
      var data = state as Data;
      double dataOnStack = data.Property;
      for (int count = 0; count < 100000000; count++)
      {
         dataOnStack++;
      }
      data.Property = dataOnStack;
   }

   public class Data
   {
      public double Property
      {
         get;
         set;
      }
   }
}

This code was run on a Core 2 Quad (4 core system) with the following results:

One thread using heap: 00:00:01.8125000

Four threads using heap: 00:00:17.7500000

One thread using stack: 00:00:00.3437500

Four threads using stack: 00:00:00.3750000

So using the heap with four threads did 4 times the work but took almost 10 times as long. This means it would be twice as fast in this case to use only one thread??????

Using the stack was much more as expected.

I would like to know what is going on here. Can the heap only be written to from one thread at a time?

like image 672
trampster Avatar asked Dec 03 '22 08:12

trampster


1 Answers

The answer is simple - run outside of Visual Studio...

I just copied your entire program, and ran it on my quad core system.

Inside VS (Release Build):

One thread using heap: 00:00:03.2206779
Four threads using heap: 00:00:23.1476850
One thread using stack: 00:00:00.3779622
Four threads using stack: 00:00:00.5219478

Outside VS (Release Build):

One thread using heap: 00:00:00.3899610
Four threads using heap: 00:00:00.4689531
One thread using stack: 00:00:00.1359864
Four threads using stack: 00:00:00.1409859

Note the difference. The extra time in the build outside VS is pretty much all due to the overhead of starting the threads. Your work in this case is too small to really test, and you're not using the high performance counters, so it's not a perfect test.

Main rule of thumb - always do perf. testing outside VS, ie: use Ctrl+F5 instead of F5 to run.

like image 163
Reed Copsey Avatar answered Dec 04 '22 22:12

Reed Copsey