Reading the documentation on Task Parallelism and Begin, I wondered what the specification for the number of tasks a "begin" statement or a "begin" block will create. "Cobegin", as described, creates a task for each statement.
I wondered if there was a ~~parallel~~ between cobegin and begin as exists between forall and coforall (number of threads set in environment variable or number of statements or iterators, respectively). Additionally, I was curious if there exist similar restrictions on what can be done when forking on a begin versus a cobegin like the forall paradigms.
For example, a forall loop must be able to be executed sequentially and each thread cannot access and modify another. How many tasks will a begin statement create and does it hold the same requirements as forall?
My simple test shown below. That second test (with the cobegin) did not function correctly when it was just a begin. The documentation states that the behavior of begin is that the main thread continues after that block immediately, however this says to me there may be similar restrictions or expectations for begin statements as for forall loops.
// https://chapel-lang.org/docs/primers/taskParallel.html
use Time;
config const dur : real(64) = 3.0;
config const run : bool = false;
proc sayHello() {
writeln("Hello there!");
}
proc sayGoodbye() {
writeln("Goodbye!");
}
proc main()
{
// A blocking task parallel segment
// Expect no specific order of processing
// except where logical within a function or block {...}
//
// Outputs in this case can still be interleaved, as shown with C
cobegin {
// A
sayHello();
// B
sayGoodbye();
// C
{
writeln("I'm a block");
writeln("^ and this message will come below");
}
}
writeln("\nI wait until everything in the above block is done.");
if !run then exit(0);
var a : atomic uint(64) = 0;
var b : atomic bool = false;
begin cobegin {
// Creates a stopwatch and reads the value of atomic int a
{
var it : uint(64) = 0;
var t : stopwatch;
t.start();
while (t.elapsed() < dur) {
if (it % 1000000 == 0) {
writeln("The value I read is ", a.read());
}
it += 1;
}
t.stop();
b.write(true);
writeln("\nThe amount of time passed: ", t.elapsed());
writeln("And the final value I read is ", a.read());
}
// Task to constantly increment an atomic int
{
while(!b.read()) {
a.write(a.read()+1);
}
}
}
writeln("Out here!");
var t2 : stopwatch;
t2.start();
// Creates a task for EACH index
// 100,000 tasks is slow
coforall i in 1..#100000 {
if (i % 5000 == 0) {
writeln(i);
}
}
writeln("Time elapsed outside: ", t2.elapsed());
t2.stop();
}
thanks for the question.
To answer your most specific question, a
beginstatement will always create a single new task to run the statement that it prefixes. Thus,will create a single task to run
foo()while the original task goes on to executebar()(and whatever may follow).Note that this is true whether the statement that follows is a singleton statement (like
foo();above) or a compound statement as in this example:Specifically, this program will create a single task that will run all of the code in the block, as in any normal compound statement—that is, it will run
foo()thenboo()thengoo()sequentially before terminating. Meanwhile the original task goes on to executebar()while the new one is doing this.This is in contrast with the
cobeginstatement which can only be used to prefix a compound statement, creating a distinct task for each child statement within it. Thus:creates three tasks, one for each of
foo(),boo(), andgoo(), then waits for them all to complete before going on to executebar().Mixing the two as you did:
creates one task to execute the
cobeginwhile the second task goes on to runbar(). Meanwhile that first task hitting thecobegincreates three more tasks to execute each offoo(),bar(), andgoo().Your question points out an unintended syntactic symmetry between
begin::cobeginandforall::coforallthat I somehow never seem to have noticed until you asked today. That is, where the second two are implicit vs. explicit parallel loop forms, the first two might be considered implicit vs. explicit ways of executing a compound statement.However, that is not the intention, and the reality is that
beginalways creates a single task andcobeginalways decorates a compound statement and creates a task per child statement. I usually think ofbegin,cobegin, andcoforallas being ways to create 1, several, or arbitrarily many explicit tasks; and forforallto be in its own category, invoking a parallel iterator that creates as many tasks as it wishes.