review of std.parallelism

Michel Fortin michel.fortin at michelf.com
Sat Mar 19 11:14:51 PDT 2011


On 2011-03-19 13:20:00 -0400, dsimcha <dsimcha at yahoo.com> said:

> On 3/19/2011 1:09 PM, Michel Fortin wrote:
>> For instance:
>> 
>> void main() {
>> int sum = 0;
>> foreach (int value; taskPool.parallel([0,2,3,6,1,4,6,3,3,3,6])) {
>> sum += value;
>> }
>> writeln(sum);
>> }
>> 
>> The "+=" would need to be an atomic operation to avoid low-level races.
>> 
>> I think that ParallelForeach's opApply should only accept a shared
>> delegate. I define shared delegate as a delegate that does not reference
>> any non-shared variables of its outer scope. The problem is that DMD
>> currently doesn't know how to determine whether a delegate literal is
>> shared or not, thus a delegate literal is never shared and if
>> ParallelForeach's opApply asked a shared delegate as it should it would
>> just not work. Fix DMD to create shared delegate literals where
>> appropriate and everything can be guarantied race-free.
> 
> If you want pedal-to-metal parallelism without insane levels of 
> verbosity, you can't have these safety features.

I'm not sure where my proposal asks for more verbosity or less 
performance. All I can see is a few less casts in std.parallelism and 
that it'd disallow the case in my example above that is totally wrong. 
Either you're interpreting it wrong or there are things I haven't 
thought about (and I'd be happy to know about them).

But by looking at all the examples in the documentation, I cannot find 
one that would need to be changed... well, except the one I'll discuss 
below.


> I thought long and hard about this issue before submitting this lib for 
> review and concluded that any solution would make std.parallelism so 
> slow, so limited and/or such a PITA to use that I'd rather it just punt 
> these issues to the programmer.  In practice, parallel foreach is used 
> with very small, performance-critical snippets that are fairly easy to 
> reason about even without any language-level race safety.

I'm not too convinced about the "I know what I'm doing" argument when I 
look at this example from asyncBuf's documentation:

    auto lines = File("foo.txt").byLine();
    auto duped = map!"a.idup"(lines);  // Necessary b/c byLine() 
recycles buffer

    // Fetch more lines in the background while we process the lines already
    // read into memory into a matrix of doubles.
    double[][] matrix;
    auto asyncReader = taskPool.asyncBuf(duped);

    foreach(line; asyncReader) {
        auto ls = line.split("\t");
        matrix ~= to!(double[])(ls);
    }

Look at the last line of the foreach. You are appending to a non-shared 
array from many different threads. How is that not a race condition?

With my proposal, the compiler would have caught that because opApply 
would want the foreach body to be  a shared delegate, and 
reading/writing to non-shared variable "matrix" in the outer scope from 
a shared delegate literal would be an error.

I'm not too sure how hard it'd be to do that in the compiler, but I 
think it's the right thing to do. Once the compiler can do this we can 
have safety; until that time I'd agree to see std.parallelism stay 
unsafe.

-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/



More information about the Digitalmars-d mailing list