performance

Generic List converter to multidimensional (2D) string array (for excel data population)

Posted on Updated on

This post is still related to the excel data population that I posted 2 weeks back where I concluded that the most performant way for the data population is using multidimensional string array (multi cells & rows).

Let’s first take a look at the SampleObject class that we use to populate the data into excel

class SampleObject
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    public string D { get; set; }
    public string E { get; set; }
}

Now in the previous post, the data population is being done “statically” compiled, means it only supports for that exact SampleObject type/class.

static void PopulateItemsWithMultiCellsAndRowsMethod(Worksheet worksheet, IList<SampleObject> items)
{
    LogFormat("Start populating {0} rows using multi cells and rows method", items.Count.ToString());
    var stringArray = new string[items.Count, 5];
    for (int i = 0; i < items.Count; i++)
    {
        var item = items[i];
        var rowIndex = i + 1;
        stringArray[i, 0] = item.A;
        stringArray[i, 1] = item.B;
        stringArray[i, 2] = item.C;
        stringArray[i, 3] = item.D;
        stringArray[i, 4] = item.E;
    }
 
    var r = worksheet.get_Range(string.Format("A{0}:E{1}", 1, items.Count));
    r.Value = stringArray;
    DisposeCOMObject(r);
}

The above will work just fine until there are other “SampleObjectX”s that need to be supported, so it will be more convenient if we can “dynamically” do this for any given objects.

One obvious answer will be using Reflection but there is also another alternative which is generating dynamic methods using Expression Tree. There are pros and cons between the 2 options above (nicely explained by this post).

In below code snippet, I summarized the 3 approaches

1. Statically

string[,] ConvertTo2DStringArrayStatically(IList<SampleObject> items)
{   
    var stringArray = new string[items.Count, 5];
    for (int i = 0; i < items.Count; i++)
    {
        var item = items[i];
        stringArray[i, 0] = item.A;
        stringArray[i, 1] = item.B;
        stringArray[i, 2] = item.C;
        stringArray[i, 3] = item.D;
        stringArray[i, 4] = item.E;
    }
    return stringArray;
}

2. With reflection

string[,] ConvertTo2DStringArrayWithReflection<T>(IList<T> items)
{
    var props = typeof(T).GetProperties();
    var stringArray = new string[items.Count, props.Length];

    for (int i = 0; i < items.Count; i++)
    {
        var item = items[i];
        for (int iProp = 0; iProp < props.Length; iProp++)
        {
            stringArray[i, iProp] = props[iProp].GetValue(item).ToString();
        }
    }
    return stringArray;
}

3. With Expression Tree (no cache is added intentionally for brevity purpose)

Func<IList<T>, string[,]> ConvertTo2DStringArrayWithExpressionTree<T>()
{
    var t = typeof(T);
    var tProperties = t.GetProperties();

    var statements = new List<Expression>();

    var listInputParameter = Expression.Parameter(typeof(IList<T>));
    var itemPropertiesCount = Expression.Constant(tProperties.Length);
    var rowCountExpression = Expression.Property(listInputParameter, typeof(ICollection<T>), "Count");
    var arrayIndexVariable = Expression.Variable(typeof(int));
    var currentItem = Expression.Variable(t);
    
    statements.Add(Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string), typeof(string) })
                    , Expression.Constant("Converting {0} rows with expression tree")
                    , Expression.Call(rowCountExpression, typeof(object).GetMethod("ToString"))));

    // Initialize string array based on the items row count and the 
    var stringArrayVariable = Expression.Variable(typeof(string[,]));
    var newArrayExpression = Expression.NewArrayBounds(typeof(string), rowCountExpression, itemPropertiesCount);
    var initializeArray = Expression.Assign(stringArrayVariable, newArrayExpression);
    statements.Add(initializeArray);

    // Prepare item assignments here as it requires loop on the properties which can't be done inline
    var itemStatements = new List<Expression>();
    itemStatements.Add(Expression.Assign(currentItem, Expression.Property(listInputParameter, "Item", arrayIndexVariable)));
    for (int pIndex = 0; pIndex < tProperties.Length; pIndex++)
    {
        var propExpression = Expression.Property(currentItem, tProperties[pIndex]);
        itemStatements.Add(Expression.Assign(Expression.ArrayAccess(stringArrayVariable, new List<Expression> { arrayIndexVariable, Expression.Constant(pIndex) }), propExpression));
    }
    itemStatements.Add(Expression.PostIncrementAssign(arrayIndexVariable));

    // iterate the items
    var label = Expression.Label();
    var forLoopBody = Expression.Block(
        new[] { arrayIndexVariable }, // local variable
        Expression.Assign(arrayIndexVariable, Expression.Constant(0)), // initialize with 0
        Expression.Loop(
            Expression.IfThenElse(
                Expression.LessThan(arrayIndexVariable, rowCountExpression), // test
                Expression.Block(new[] { currentItem }, itemStatements), // execute if true
                Expression.Break(label)) // execute if false
            , label));
    statements.Add(forLoopBody);

    // return statement
    statements.Add(stringArrayVariable); 

    var body = Expression.Block(stringArrayVariable.Type, new[] { stringArrayVariable }, statements.ToArray());
    var compiled = Expression.Lambda<Func<IList<T>, string[,]>>(body, listInputParameter).Compile();
    return compiled;
}

the expression tree “body” in DebugView

.Block(System.String[,] $var1) {
    .Call System.Console.WriteLine("Converting {0} rows with expression tree", .Call ($var2.Count).ToString());
    $var1 = .NewArray System.String[$var2.Count,5];
    .Block(System.Int32 $var3) {
        $var3 = 0;
        .Loop  {
            .If ($var3 < $var2.Count) {
                .Block(ExcelDataPopulator.SampleObject $var4) {
                    $var4 = $var2.Item[$var3];
                    $var1[$var3,0] = $var4.A;
                    $var1[$var3,1] = $var4.B;
                    $var1[$var3,2] = $var4.C;
                    $var1[$var3,3] = $var4.D;
                    $var1[$var3,4] = $var4.E;
                    $var3++
                }
            } .Else {
                .Break #Label1 { }
            }
        }
        .LabelTarget #Label1:
    };
    $var1
}

From the performance benchmark side,

var sw = Stopwatch.StartNew();

var items = Enumerable.Range(1, 1000000).Select(i => new SampleObject { A = "A" + i, B = "B" + i, C = "C" + i, D = "D" + i, E = "E" + i }).ToList();
string[,] stringArray = null;

LogFormat("Initialized {0} rows, {1} ms", items.Count.ToString(), sw.ElapsedMilliseconds.ToString());
sw.Restart();

for (int i = 0; i < 10; i++)
    stringArray = ConvertTo2DStringArrayStatically(items);

LogFormat("Convert with statically is completed in {0} ms", sw.ElapsedMilliseconds.ToString());
sw.Restart();

for (int i = 0; i < 10; i++)
    stringArray = ConvertTo2DStringArrayWithReflection(items);

LogFormat("Convert with reflection is completed in {0} ms", sw.ElapsedMilliseconds.ToString());
sw.Restart();

for (int i = 0; i < 10; i++)
    stringArray = ConvertTo2DStringArrayWithExpressionTree<SampleObject>()(items);

LogFormat("Convert with expression tree is completed in {0} ms", sw.ElapsedMilliseconds.ToString());

// log output:
//Initialized 1,000,000 rows, 1,747 ms
//Convert with statically is completed in 1,077 ms
//Convert with reflection is completed in 10,915 ms
//Convert with expression tree is completed in 1,617 ms

So conclusions:

  1. statically will always be the most performant but maintainability is a bit of a challenging if we have many objects/types to support.
  2. reflection is 10x slower but the code is much simpler
  3. dynamic method via expression tree is having more code to build the expressions but the performance is only slightly slower than statically compiled option.

There are couple of more options available that I didn’t cover above:

  1. Code generation using class metadata/reflection
  2. Reflection.Emit

Full source code is available at https://github.com/bembengarifin/ExcelDataPopulator)

Advertisements

Ways to populate data into excel via C# and excel interop (with performance comparison)

Posted on Updated on

Few days ago I was looking into some excel automation code where I need to fetch the data from the back end and then write/populate the data into excel.

There are 3 ways that we can populate the data into excel via excel interop.

1. Single cell

var r1 = worksheet.get_Range("A1"); // single cell
r1.Value = "A1";

2. Multi cells for 1 row

var r2 = worksheet.get_Range("A2:C2"); // multi cells in 1 row
r2.Value = new[] { "A2", "B2", "C2" };

3. Multi cells & rows

var r3 = worksheet.get_Range("A3:C4"); // multi cells & rows
r3.Value = new string[,] { { "A3", "B3", "C3" }, { "A4", "B4", "C4" } }; ;

As the work is related to large volume of data (100K rows), I looked further into the options above to find the most performant one.
So I came up with a list of 100k rows very simple object (5 properties), gave each option a run, and got the stats:

Start populating 100,000 rows using single cell method
Populate data completed in 196,206 ms

Start populating 100,000 rows using multi cells (per 1 row) method
Populate data completed in 43,039 ms

Start populating 100,000 rows using multi cells and rows method
Populate data completed in 1,028 ms

Sample output

Excel100kRows

Quite staggering differences between the options and I’m quite impressed with the excellent performance of the multi cells/rows option.

Included the code snippets below for quick view (full source code is also available at https://github.com/bembengarifin/ExcelDataPopulator)

static void PopulateItemsWithSingleCellMethod(Worksheet worksheet, IList<SampleObject> items)
{
    LogFormat("Start populating {0} rows using single cell method", items.Count.ToString());
    for (int i = 0; i < items.Count; i++)
    {
        var item = items[i];
        var rowIndex = i + 1;
        var r1 = worksheet.get_Range("A" + rowIndex); r1.Value = item.A; DisposeCOMObject(r1);
        var r2 = worksheet.get_Range("B" + rowIndex); r2.Value = item.B; DisposeCOMObject(r2);
        var r3 = worksheet.get_Range("C" + rowIndex); r3.Value = item.C; DisposeCOMObject(r3);
        var r4 = worksheet.get_Range("D" + rowIndex); r4.Value = item.D; DisposeCOMObject(r4);
        var r5 = worksheet.get_Range("E" + rowIndex); r5.Value = item.E; DisposeCOMObject(r5);
    }
}

static void PopulateItemsWithMultiCellsOneRowMethod(Worksheet worksheet, IList<SampleObject> items)
{
    LogFormat("Start populating {0} rows using multi cells (per 1 row) method", items.Count.ToString());
    for (int i = 0; i < items.Count; i++)
    {
        var item = items[i];
        var rowIndex = i + 1;
        var r = worksheet.get_Range(string.Format("A{0}:E{0}", rowIndex));
        r.Value = new[] { item.A, item.B, item.C, item.D, item.E };
        DisposeCOMObject(r);
    }
}

static void PopulateItemsWithMultiCellsAndRowsMethod(Worksheet worksheet, IList<SampleObject> items)
{
    LogFormat("Start populating {0} rows using multi cells and rows method", items.Count.ToString());
    var stringArray = new string[items.Count, 5];
    for (int i = 0; i < items.Count; i++)
    {
        var item = items[i];
        var rowIndex = i + 1;
        stringArray[i, 0] = item.A;
        stringArray[i, 1] = item.B;
        stringArray[i, 2] = item.C;
        stringArray[i, 3] = item.D;
        stringArray[i, 4] = item.E;
    }

    var r = worksheet.get_Range(string.Format("A{0}:E{1}", 1, items.Count));
    r.Value = stringArray;
    DisposeCOMObject(r);
}

protobuf-net v2 performance test

Posted on Updated on

This is a continuation from my previous post of Run time initialisation of protobuf-net v2 without annotation

I listed couple of ways to use protobuf-net v2:

  1. annotation on the class (ProtoContractAttribute) and property (ProtoMemberAttribute)
  2. annotation only on the class (ProtoContractAttribute) with ImplicitFields named parameter
  3. run time registration using the default model (RuntimeTypeModel.Default)
  4. run time registration with separate type model which supports run time compilation (from the method description -> Fully compiles the current model into a static-compiled serialization dll)
  5. precompiled serializer (this is not really a different method, but a further optimisation to the 1st and 2nd options above)

Based on the list above, I created some performance tests using a class defined with 150+ properties (mixed with string, custom classes) to make it closer to real life entity that I have in the project.
And below is the statistics produced by the test for different collection sizes, ordered by the most “efficient” ones to regular binary serialization for comparison purpose.

Conclusions:

  • Performance : Precompiled serializer seems to be the notable performant option, although as you can see below, the other protobuf options are not very far off from one to another as well
  • Size : ImplicitFields seems to be the smallest in size, although it’s not far off to other options, although the run time compiled model option seems to be on the higher end.

Please note that this benchmark below is purely for my curiosity purpose, please do NOT refer this as a guidelines for any of your application.
The main reason why I’m saying the above is because I actually found somewhat different statistics when I tried to run this on the real class/model being used in my project.
The performance is somewhat as below between, however the size was the quite surprising factor where the memory size for the run time registration was 50% higher than the annotation option hence we ended up sticking up with the full annotation option as size is a very important factor as we need to send the data through the wire hence any significant size increase will introduce latency.
So again, moral of the story, please ALWAYS run a benchmark using your real project before jumping into any conclusion.
Attached the sample project again below for reference as usual.

Sample Project


-------------------------------
TestProtobufPreCompiledTypeModel
100 items, Size: 238905, Completed: 30 ms, Serialization: 6 ms, Deserialization: 13 ms
1000 items, Size: 2389006, Completed: 378 ms, Serialization: 69 ms, Deserialization: 161 ms
10000 items, Size: 23890006, Completed: 4195 ms, Serialization: 675 ms, Deserialization: 1806 ms
-------------------------------
TestProtobufRuntimeRegistrationWithCompiledTypeModel
100 items, Size: 273805, Completed: 42 ms, Serialization: 7 ms, Deserialization: 24 ms
1000 items, Size: 2738006, Completed: 424 ms, Serialization: 76 ms, Deserialization: 191 ms
10000 items, Size: 27380006, Completed: 4634 ms, Serialization: 762 ms, Deserialization: 2151 ms
-------------------------------
TestProtobufRuntimeRegistration
100 items, Size: 238000, Completed: 35 ms, Serialization: 8 ms, Deserialization: 16 ms
1000 items, Size: 2380000, Completed: 415 ms, Serialization: 78 ms, Deserialization: 173 ms
10000 items, Size: 23800000, Completed: 4692 ms, Serialization: 785 ms, Deserialization: 2182 ms
-------------------------------------
TestProtobufImplicitAnnotatedEntities
100 items, Size: 237700, Completed: 35 ms, Serialization: 7 ms, Deserialization: 17 ms
1000 items, Size: 2377000, Completed: 429 ms, Serialization: 78 ms, Deserialization: 201 ms
10000 items, Size: 23770000, Completed: 4799 ms, Serialization: 796 ms, Deserialization: 2207 ms
----------------------------------
TestProtobufFullyAnnotatedEntities
100 items, Size: 238900, Completed: 33 ms, Serialization: 8 ms, Deserialization: 14 ms
1000 items, Size: 2389000, Completed: 423 ms, Serialization: 79 ms, Deserialization: 166 ms
10000 items, Size: 23890000, Completed: 4734 ms, Serialization: 782 ms, Deserialization: 2243 ms
------------------
TestBinaryEntities
100 items, Size: 426909, Completed: 82 ms, Serialization: 30 ms, Deserialization: 36 ms
1000 items, Size: 4183509, Completed: 1537 ms, Serialization: 384 ms, Deserialization: 997 ms
10000 items, Size: 41749512, Completed: 89895 ms, Serialization: 4173 ms, Deserialization: 83900 ms

Run time initialisation of protobuf-net v2 without annotation

Posted on

I have been using protobuf-net for the project at office but it’s only implemented for a limited number of entities (or business objects). We’re now trying to see how to implement this to the whole entities that we’re sending back n forth between the UI and the back end.

As we’re currently using a code generation “tool” to produce the code, we’re thinking to incorporate the annotation/attribute into the generated class/property code, however due to some not really technical reason, this is a bit complicated to achieve.

I thought of checking the site again to get some idea and I came across to this statement “allow use without attributes if you wish” on the page.

Then I tried to dig out more on the SO and found some more good leads on this. Apparently it’s very much possible to perform “registration” during run time mode which allows us NOT to use any annotation on the entities.

So thanks to the pointers, there are 2 alternatives for this:
1. With very minimal annotation on the class level

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] // only required on the class level
class PersonEntity
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
}

2. Without any annotation (using RuntimeTypeModel)

static void InitializeProtobufRunTime()
{
    var assembly = Assembly.GetAssembly(typeof(PlainEntities.PersonEntity));
    var types = assembly.GetTypes();
    foreach (var t in types.Where(x => x.Namespace.Contains("PlainEntities")))
    {
        Console.WriteLine("Processing {0}", t.FullName);
        var meta = RuntimeTypeModel.Default.Add(t, false);
        var index = 1;

        // find any derived class for the entity
        foreach (var d in types.Where(x => x.IsSubclassOf(t)))
        {
            var i = index++;
            Console.WriteLine("\tSubtype: {0} - #{1}", d.Name, i);
            meta.AddSubType(i, d);
        }

        // then add the properties
        foreach (var p in t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(x => x.GetSetMethod() != null))
        {
            var i = index++;
            Console.WriteLine("\tProperty: {0} - #{1}", p.Name, i);
            meta.AddField(i, p.Name);
        }
    }
}

And both the above works quite well without any performance differences.


------------------
TestBinaryEntities
------------------
Process: 100000 items, MemorySize: 7400705, Completed in: 3877 ms, Serialization took: 676 ms, Deserialization took: 2948 ms

----------------------------------
TestProtobufFullyAnnotatedEntities
----------------------------------
Process: 100000 items, MemorySize: 3983490, Completed in: 682 ms, Serialization took: 164 ms, Deserialization took: 253 ms

-------------------------------------
TestProtobufImplicitAnnotatedEntities
-------------------------------------
Process: 100000 items, MemorySize: 3983490, Completed in: 595 ms, Serialization took: 104 ms, Deserialization took: 210 ms

-------------------------------
TestProtobufRuntimeRegistration
-------------------------------
Processing ProtobufTestConsole.PlainEntities.BaseEntity
Subtype: PersonEntity - #1
Property: Id - #2
Property: Gender - #3
Processing ProtobufTestConsole.PlainEntities.PersonEntity
Property: FirstName - #1
Property: LastName - #2
Property: Age - #3
Process: 100000 items, MemorySize: 4083490, Completed in: 646 ms, Serialization took: 113 ms, Deserialization took: 232 ms

Looking forward to get this in :)

Also attached the sample project for reference

Sample Project

Controlling the concurrency level using MaxDegreeOfParallelism in Parallel

Posted on

This is another nice available in the box feature when using the Parallel class, is the ability to basically limit the concurrency for the actions that we want to execute in parallel.

Why would we ever want to do this? wouldn’t it be best if we parallel some activities as many as possible?

For certain cases, we might want to be careful for not creating too many sudden requests at the same time, a good example will be if we’re triggering a long running process in our application server, you wouldn’t want to spawn too many requests at the same time to the server, as the server may not be able to handle so many requests at the same time and not surprisingly go down due to overload. This can be categorized as DOS attack, your back end guys will hate you for this, trust me :p

To set the concurrency level of the actions that you’re going to invoke with the Parallel class is pretty simple.

Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = concurrencyLevel }, actions);

Find more about the MaxDegreeOfParallelism property in msdn.
We don’t have this such of property in the TaskCreationOptions that we can simply pass for Task, but there’s a LimitedConcurrencyLevelTaskScheduler nicely available which we can achieve the same result below where i basically spawn 10 processes with 2 max concurrency level.


15-Apr-2011 21:04:33.948 - Thead#13 - Creating 10 process definitions
15-Apr-2011 21:04:33.948 - Thead#13 - Start queueing and invoking all 10 processes
15-Apr-2011 21:04:33.948 - Thead#13 - Doing something here
15-Apr-2011 21:04:33.948 - Thead#14 - Doing something here
15-Apr-2011 21:04:34.964 - Thead#13 - Doing something here
15-Apr-2011 21:04:34.964 - Thead#14 - Doing something here
15-Apr-2011 21:04:35.964 - Thead#13 - Doing something here
15-Apr-2011 21:04:35.964 - Thead#14 - Doing something here
15-Apr-2011 21:04:36.964 - Thead#13 - Doing something here
15-Apr-2011 21:04:36.964 - Thead#14 - Doing something here
15-Apr-2011 21:04:37.964 - Thead#13 - Doing something here
15-Apr-2011 21:04:37.964 - Thead#14 - Doing something here
15-Apr-2011 21:04:38.964 - Thead#13 - All processes have been completed

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ParallelTests
{
    [TestClass]
    public class TaskSchedulerConcurrencyTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            var p = new ProcessorUsingParallel();
            p.DoProcess(processToCreate: 10, concurrencyLevel: 2);
        }
    }

    public class ProcessorUsingParallel
    {
        public void DoProcess(int processToCreate, int concurrencyLevel)
        {
            SetTurboMode();

            Debug("Creating {0} process definitions", processToCreate.ToString());

            var actions = new Action[processToCreate];
            for (int i = 0; i < processToCreate; i++)
            {
                actions[i] = () => DoSomething(1000);
            }

            Debug("Start queueing and invoking all {0} processes", processToCreate.ToString());
            var options = new ParallelOptions();
            options.MaxDegreeOfParallelism = concurrencyLevel;
            //options.TaskScheduler = new LimitedConcurrencyLevelTaskScheduler(concurrencyLevel); -- we can achieve the same result with this
            Parallel.Invoke(options, actions);

            Debug("All processes have been completed");
        }

        private void DoSomething(int Sleep)
        {
            Debug("Doing something here");
            Thread.Sleep(Sleep);
        }

        /// <summary>
        /// oh i just wish the framework would have this in place like Console.WriteLine
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        private static void Debug(string format, params object[] args)
        {
            System.Diagnostics.Debug.WriteLine(
                string.Format("{0} - Thead#{1} - {2}",
                    DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss.fff"),
                    Thread.CurrentThread.ManagedThreadId.ToString(),
                    string.Format(format, args)));
        }
        /// <summary>
        /// This is not intended for production purpose
        /// </summary>
        private static void SetTurboMode()
        {
            int t, io;
            ThreadPool.GetMaxThreads(out t, out io);
            Debug("Default Max {0}, I/O: {1}", t, io);

            var success = ThreadPool.SetMinThreads(t, io);
            Debug("Successfully set Min {0}, I/O: {1}", t, io);
        }
    }
}

Using Parallel in .Net 4.0 without worrying WaitHandle.WaitAll 64 handles limitation

Posted on

As my previous post was providing an example of using the Task class from the TPL library to overcome the 64 waithandles limitation on WaitHandle.WaitAll, here’s the other alternative of code which leverage the Parallel class from the same library. Here’s the download link for the whole test project.

However, notice one thing below that I’m using ConcurrentBag<T> type which is a thread-safe bag implementation, optimized for scenarios where the same thread will be both producing and consuming data stored in the bag (taken from the msdn page).

An interesting fact with this, it takes around 74 secs to complete where if we’re not concern about return values (remove the usage of the ConcurrentBag<T>), it will only take around 28 seconds where using Task (with or without return values), it will only take 22 secs. I guess we can probably use a “state object” to pass into the invocation to hold the values for each process to avoid the locking costs, but since Task is already doing this well, it will need a justification why we need to do that manually ourselves with Parallel.Invoke solution. Moral of the story: always get the facts first by each alternative for your solution then decision will be easy and justifiable.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;

namespace ParallelismTests
{
    [TestClass]
    public class ParallelTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            var p = new ProcessorUsingParallel();
            var counts = p.DoProcess(hiLevelParallelism: 1000, loLevelParallelism: 1000);

            Assert.AreEqual(1000 * 1000, counts);
        }
    }

    public class ProcessorUsingParallel
    {
        public int DoProcess(int hiLevelParallelism, int loLevelParallelism)
        {
            Utility.SetTurboMode();

            Utility.Debug("Start queueing {0} high level processes", hiLevelParallelism.ToString());

            var counts = new ConcurrentBag<int>();
            var r = new Random();

            var actions = new Action[hiLevelParallelism];
            for (int i = 0; i < hiLevelParallelism; i++)
            {
                actions[i] = () => counts.Add(DoHighLevelProcess(r, loLevelParallelism));
            }

            Utility.Debug("Invoking all {0} high level tasks", hiLevelParallelism.ToString());
            Parallel.Invoke(actions);

            Utility.Debug("All processes have been completed");
            return counts.Sum(t => t);
        }

        private int DoHighLevelProcess(Random r, int loLevelParallelism)
        {
            var counts = new ConcurrentBag<int>();
            var actions = new Action[loLevelParallelism];
            for (int i = 0; i < loLevelParallelism; i++)
            {
                actions[i] = () => counts.Add(DoLowLevelProcess(r.Next(1, 10)));
            }
            Parallel.Invoke(actions);

            Utility.Debug("DoHighLevelProcess - Completed with {0} LowLeveProcesses", loLevelParallelism);
            return counts.Sum(t => t);
        }
        private int DoLowLevelProcess(int Sleep)
        {
            Thread.Sleep(Sleep);
            return 1;
        }
    }
}

Using Task in .Net 4.0 without worrying WaitHandle.WaitAll 64 handles limitation

Posted on

In my previous post, I was looking into having 2 level of concurrent processes, creating concurrent processes which will spawn another concurrent processes each.

Now by using WaitHandle.WaitAll, we can see there’s a limit of 64 waithandles for waiting, so with the new Task Parallel Library in .Net 4.0, we don’t have to worry about that kind of limitation anymore.

Using the code in the previous post, I then used the Task class to perform the concurrent processes. Notice the code below, I’m actually create 1.000 high level processes which will create 1.000 low level processes each, so in total 1.000.000 processes. And another very nice thing here is that we can actually return a value from each task very easily compared to the previous solution using Threadpool.

One more interesting here is that, noticed the line#40 & #54: Task.WaitAll(tasks.ToArray());, with that line in place, it would only take 20+ seconds to complete where without it (remove/comment those lines), it would then take 11 minutes. That a pretty huge difference ;)

Reason: I think it makes sense, because without that line, we will be waiting for the task completion sequentially in the SUM operation when Task.Result was being called, where the completion of 1 task with the next/previous task in the collection might be different, where Task.WaitAll will basically get all the signals by each task as when it completed, so when the SUM operation is being called, all the results are already available without any need to wait anymore :)

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
using System.Threading;

namespace ParallelismTests
{
    [TestClass]
    public class TaskTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            var p = new ProcessorUsingTask();
            var counts = p.DoProcess(hiLevelParallelism: 1000, loLevelParallelism: 1000);

            Assert.AreEqual(1000*1000, counts);
        }
    }

    public class ProcessorUsingTask
    {
        public int DoProcess(int hiLevelParallelism, int loLevelParallelism)
        {
            SetTurboMode();

            Debug("Start queueing {0} high level processes", hiLevelParallelism.ToString());

            var tasks = new List<Task<int>>();
            var r = new Random();
            for (int i = 0; i < hiLevelParallelism; i++)
            {
                var task = Task.Factory.StartNew<int>(() => DoHighLevelProcess(r, loLevelParallelism));
                tasks.Add(task);
            }

            Debug("Waiting for all {0} high level tasks to complete", hiLevelParallelism.ToString());
            Task.WaitAll(tasks.ToArray()); // try comment this line out and see the performance impact :)

            Debug("All processes have been completed");
            return tasks.Sum(t => t.Result);
        }

        private int DoHighLevelProcess(Random r, int loLevelParallelism)
        {
            var tasks = new List<Task<int>>();
            for (int i = 0; i < loLevelParallelism; i++)
            {
                var task = Task.Factory.StartNew<int>(() => DoLowLevelProcess(r.Next(1, 10)));
                tasks.Add(task);
            }
            Task.WaitAll(tasks.ToArray()); // try comment this line out and see the performance impact :)
            
            Debug("DoHighLevelProcess - Completed with {0} LowLeveProcesses", loLevelParallelism);
            return tasks.Sum(t => t.Result);
        }
        private int DoLowLevelProcess(int Sleep)
        {
            Thread.Sleep(Sleep);
            //Debug("DoLowLevelProcess - Completed after {0} ms", Sleep.ToString());
            return 1;
        }

        /// <summary>
        /// oh i just wish the framework would have this in place like Console.WriteLine
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        private static void Debug(string format, params object[] args)
        {
            System.Diagnostics.Debug.WriteLine(
                string.Format("{0} - Thead#{1} - {2}",
                    DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss.fff"),
                    Thread.CurrentThread.ManagedThreadId.ToString(),
                    string.Format(format, args)));
        }
        /// <summary>
        /// This is not intended for production purpose
        /// </summary>
        private static void SetTurboMode()
        {
            int t, io;
            ThreadPool.GetMaxThreads(out t, out io);
            Debug("Default Max {0}, I/O: {1}", t, io);

            var success = ThreadPool.SetMinThreads(t, io);
            Debug("Successfully set Min {0}, I/O: {1}", t, io);
        }
    }
}