Are you using Parallel Extensions?

Viewed 652

I hope this is not a misuse of stackoverflow; recently I've seen some great questions here on Parallel Extensions, and it got my interest piqued.

My question: Are you using Parallel Extensions, and if so, how?

My name is Stephen Toub and I'm on the Parallel Computing Platform team at Microsoft. We're the group responsible for Parallel Extensions. I'm always interested in hearing about how developers are utilizing Parallel Extensions (e.g. Parallel.For, PLINQ, ConcurrentDictionary, etc.), positive experiences you've had, negative experiences you've had, feature requests for the future, and so on.
If you'd be willing to share such information, please do, either here as a response to this question or to me privately through email at stoub at microsoft dot com.

I'm very much looking forward to hearing from you.

Thanks in advance!

5 Answers

I'm using the TPL for doing nested Parallel.ForEach calls. Because I access dictionaries from these calls I have to use ConcurrentDictionary. Although it's nice, I have a few issues:

  • The delegates inside of ForEach don't do much work so I don't get much parallelism. The system seems to spend most of its time joining threads. It would be nice if there was a way to figure out why it's not getting better concurrency and improve it.

  • The inner ForEach iterations are over ConcurrentDictionary instances, which would cause the system to spend much of its time enumerators for the dictionary if I didn't add an enumerator cache.

  • Many of my ConcurrentDictionary instances are actually sets, but there is no ConcurrentSet so I had to implement my own with a ConcurrentDictionary.

  • ConcurrentDictionary does not support object initialization syntax so I can't say var dict = new ConcurrentDictionary<char, int> { { 'A', 65 } }; which also means I can't assign ConcurrentDictionary literals to class members.

  • There are some places where I have to lookup a key in a ConcurrentDictionary and call an expensive function to create a value if it doesn't exist. It would be nice if there were an overload of GetOrAdd that takes an addValueFactory so that the value can be computed only if the key doesn't exist. This can be simulated with .AddOrUpdate(key, addValueFactory, (k, v) => v) but that adds the overhead of an extra delegate call to every lookup.

I haven't used it extensively yet, but I've definitely kept my ear to its uses and look for opportunities in our code base to put it to use (unfortunately, we're .NET-2.0 bound on many of our projects still for the time being). One little gem I came up with myself was a unique word counter. I think this is the fastest and most concise implementation I can come up with - if someone can make it better, that would be awesomeness:

private static readonly char[] delimiters = { ' ', '.', ',', ';', '\'', '-', ':', '!', '?', '(', ')', '<', '>', '=', '*', '/', '[', ']', '{', '}', '\\', '"', '\r', '\n' };
private static readonly Func<string, string> theWord = Word;
private static readonly Func<IGrouping<string, string>, KeyValuePair<string, int>> theNewWordCount = NewWordCount;
private static readonly Func<KeyValuePair<string, int>, int> theCount = Count;

private static void Main(string[] args)
{
    foreach (var wordCount in File.ReadAllText(args.Length > 0 ? args[0] : @"C:\DEV\CountUniqueWords\CountUniqueWords\Program.cs")
        .Split(delimiters, StringSplitOptions.RemoveEmptyEntries)
        .AsParallel()
        .GroupBy(theWord, StringComparer.OrdinalIgnoreCase)
        .Select(theNewWordCount)
        .OrderByDescending(theCount))
    {
        Console.WriteLine(
            "Word: \""
            + wordCount.Key
            + "\" Count: "
            + wordCount.Value);
    }

    Console.ReadLine();
}

private static string Word(string word)
{
    return word;
}

private static KeyValuePair<string, int> NewWordCount(IGrouping<string, string> wordCount)
{
    return new KeyValuePair<string, int>(
        wordCount.Key,
        wordCount.Count());
}

private static int Count(KeyValuePair<string, int> wordCount)
{
    return wordCount.Value;
}

I have been using it on my project MetaSharp. I have a MSBuild based compile pipeline for DSLs and one stage types is a Many to Many stage. The M:M stage uses .AsParallel.ForAll(...).

Here's the snippet:

protected sealed override IEnumerable<IContext> Process()
{
    if (this.Input.Count() > 1)
    {
        this.Input
            .AsParallel<IContext>()
            .ForAll(this.Process);
    }
    else if (this.Input.Any())
    {
        this.Process(this.Input.Single());
    }

    return this.Input.ToArray();
}

We don't use it extensively, but it has certainly come in handy.

I was able to reduce the running time of a few of our longer-running unit tests to about 1/3 their original time just by wrapping some of the more time-intensive steps in a Parallel.Invoke() call.

I also love using the parallel libraries for testing thread-safety. I've caught and reported a couple of threading issues with Ninject with code something like this:

var repositoryTypes = from a in CoreAssemblies
                    from t in a.GetTypes()
                    where t.Name.EndsWith("Repository")
                    select t;
repositoryTypes.ToList().AsParallel().ForAll(
    repositoryType => _kernel.Get(repositoryType));

In our actual production code, we use some parallel extensions to run some integration actions that are supposed to run every few minutes, and which consist mostly of pulling data from web services. This takes special advantage of parallelism because of the high latency inherent in web connections, and allows our jobs to all finish running before they're supposed to fire again.

Related