3.2. Task constructΒΆ

The programmer can specify a task using the task construct. This construct can appear inside any code block of the program, which will mark the following statement as a task.

The syntax of the task construct is the following:

#pragma oss task [clauses]
structured-block

The valid clauses for the task construct are:

  • private(<list>)
  • firstprivate(<list>)
  • shared(<list>)
  • depend(<type>: <memory-reference-list>)
  • <depend-type>(<memory-reference-list>)
  • priority(<expresion>)
  • cost(<expresion>)
  • if(<scalar-expression>)
  • final(<scalar-expresion>)
  • label(<string>)
  • [ wait | weakwait ]

The private, firstprivate and shared clauses allow to specify the data sharing of the variables referenced in the construct. A description of these clauses can be found in Data sharing attributes section.

The depend clause allows to infer additional task scheduling restrictions from the parameters it defines. These restrictions are known as dependences. The syntax of the depend clause include a dependence type, followed by colon and its associated list items. The list of valid type of dependences are defined in section Dependence model in the previous chapter. In addition to this syntax, OmpSs-2 allows to specify this information using as the name of the clause the type of dependence. Then, the following code:

#pragma oss task depend(in: a,b,c) depend(out: d)

Is equivalent to this one:

#pragma oss task in(a,b,c) out(d)

The priority clause indicates a priority hint for the task. Greater numbers indicate higher priority, and lower numbers indicate less priority. By default, tasks have priority 0. The expression of the priority is evaluated as a singed integer. This way, strictly positive priorities indicate higher priority than the default, and negative priorities indicate lower than default priority.

If the expression of the if clause evaluates to true, the execution of the new created task can be deferred, otherwise the current task must suspend its execution until the new created task has complete its execution.

If the expression of the final clause evaluates to true, the new created task will be a final tasks and all the task generating code encountered when executing its dynamic extent will also generate final tasks. In addition, when executing within a final task, all the encountered task generating codes will execute these tasks immediately after its creation as if they were simple routine calls. And finally, tasks created within a final task can use the data environment of its parent task.

Tasks with the wait clause will perform a taskwait-like operation immediately after exiting from the task code. Since it is performed outside the scope of the code of the task, this happens once the task has abandoned the stack. For this reason, its use is restricted to tasks that upon exiting do not have any subtask accessing its local variables. Otherwise, the regular taskwait shall be used instead.

The label clause defines a string literal that can be used by any performance or debugger tool to identify the task with a more human-readable format.

The following C code shows an example of creating tasks using the task construct:

float x = 0.0;
float y = 0.0;
float z = 0.0;

int main() {

  #pragma oss task
  do_computation(x);

  #pragma oss task
  {
    do_computation(y);
    do_computation(z);
  }

  return 0;
}

When the control flow reaches #pragma oss task construct, a new task instance is created, however when the program reaches return 0 the previously created tasks may not have been executed yet by the OmpSs-2 run-time.

The task construct is extended to allow the annotation of function declarations or definitions in addition to structured-blocks. When a function is annotated with the task construct each invocation of that function becomes a task creation point. Following C code is an example of how task functions are used:

extern void do_computation(float a);
#pragma oss task
extern void do_computation_task(float a);

float x = 0.0;
int main() {
   do_computation(x); //regular function call
   do_computation_task(x); //this will create a task
   return 0;
}

Invocation of do_computation_task inside main function create an instance of a task. As in the example above, we cannot guarantee that the task has been executed before the execution of the main finishes.

Note that only the execution of the function itself is part of the task not the evaluation of the task arguments. Another restriction is that the task is not allowed to have any return value, that is, the return must be void.