Intel® Advisor User Guide

ID 766448
Date 11/07/2023
Public

A newer version of this document is available. Customers should click here to go to the newest version.

Document Table of Contents

Create the Private Memory Location

The important thing is that every execution of a task must get its own private memory location to take the place of the shared memory location in the original program. This involves:

  • Creating the private memory location.

  • Replacing all uses of the shared memory location with uses of the private memory location.

How you do this will depend on what kind of shared memory location you have.

Replacing a Local Variable  

If the shared memory location is a local variable in the function containing the task's static extent, the fix is simple:

  1. Add braces around the static extent, if necessary, to make sure that it is a block.

  2. Define a new variable at the beginning of the block.

  3. Replace every use of the shared variable in the static extent with a use of the new variable.

Now each occurrence of the task will have its own copy of the local variable.

Replacing a Static or Global Variable or Class Static Data Member

Using global variables is usually a bad idea in large-scale software design. Global variables often seem like the easiest solution to a design problem, but they create obscure dependencies between parts of a program, making it harder to understand the program and harder to make changes to it. When you convert a program to run in parallel, these problems are compounded, as global variables are a prolific source of data sharing problems.

Therefore, the changes that you will make to eliminate uses of global variables are not only necessary to fix sharing problems and allow your program to run correctly in parallel. You will probably find that they would make your program more understandable and maintainable, even if you were not parallelizing it.

For example:

extern int global;
// ...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    foo(i);
    bar(i);
    ANNOTATE_TASK_END();
// ...
ANNOTATE_SITE_END(site1);
void foo(int i)
{
    global = x*3 - a[i];
}
void bar(int i)
{
    b[i] = b[i] - global;
}

The approach to creating a private replacement for a static or global variable or a class static data member is the same as for a local variable. The important difference is that global variables may be accessed in other functions in the dynamic extent of the task, so you will have to make the private replacement variable accessible to those functions, too.

  1. Define a local variable in the static extent to take the place of the shared variable, just as you do when replacing a local variable.

  2. Replace all uses of the shared variable in the static extent with uses of the new local variable.

  3. If the task calls other functions, add an additional reference parameter to each one, and pass the private variable to it. If you are programming in C, you will have to use a pointer parameter and pass the address of the variable to it.

  4. Replace all uses of the shared variable in the called functions with uses of the reference parameter.

Applying these rules to the example above, we get:

// ...
ANNOTATE_SITE_BEGIN(site1);
ANNOTATE_TASK_BEGIN(taskname);
int replacement;
foo(i, replacement);
bar(i, replacement);
ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i, int& replacement)
{
    replacement = x*3 - a[i];
}
void bar(int i, int& replacement)
{
    b[i] = b[i] - replacement;
}

Mixed-caller functions: If there are functions that are called both from the dynamic extent of the task and from outside the task, steps 3 and 4 will fix the sharing problem in the task, but they will break the calls outside the task. There are two possible solutions:

  • Modify all the calls to such functions from outside the task to pass the original variable to the new parameter.

  • Make two copies of the function: the original version, to be called from outside the task, and the one with the new parameter, to be called from inside the task.

Variables whose address is taken: The special problems of variables that are accessed through pointers are discussed in the help topic Pointer Dereferences.

More than one shared variable: The strategy described above is simple, but if you have a task with several incidentally shared variables, the multiple extra parameters are clumsy. A cleaner solution is to define a local structure variable with a field for each incidentally shared variable. Modify the functions and calls in the task's dynamic extent to pass the structure to them, and replace uses of the shared variables with uses of the appropriate field of the structure.

Creating a Task Class: If the functions in the task's dynamic extent are closely related, you might be able to create a new class which has the functions as member functions and the replacement shared variables as data members. Then the class's this pointer takes the place of the added reference parameter. Using the same example:

class TaskClass {
public:
ANNOTATE_SITE_BEGIN(site1);
    void the_task()
    {
        ANNOTATE_TASK_BEGIN(taskname);
        foo(i);
        bar(i);
        ANNOTATE_TASK_END();
    }
ANNOTATE_SITE_END(site1);
private:
    int replacement;
    void foo(int i)
    {
        replacement = x*3 - a[i];
    }
    void bar(int i)
    {
        b[i] = b[i] - replacement;
    }
};

Replacing a Structure Field

Sometimes you may have sharing problems with one or more fields in an object:

struct Point { float x, y, z; };
extern Point p;
// ...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    p.x = a[i].x * scale_x;
    p.y = a[i].y * scale_y;
    foo(i);
    ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i)
{
    b[i].x = b[i].x - p.x;
    b[i].y = b[i].y - p.y;
}

The most straightforward solution is to introduce a new local variable for the shared field:

struct Point { float x, y; };
extern Point p;
// ...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    float sub_p_x = a[i].x * scale_x;
    float sub_p_y = a[i].y * scale_y;
    foo(I, sub_p_x, sub_p_y);
    ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i, float& sub_p_x, float& sub_p_y)
{
    b[i].x = b[i].x - sub_p_x;
    b[i].y = b[i].y - sub_p_y;
}

If every shared field of the object is incidentally shared, then it will be simpler to make a single local replacement variable for the entire structure rather than a separate replacement variable for each shared field.

struct Point { float x, y; };
extern Point p;
//...
ANNOTATE_SITE_BEGIN(site1);
    ANNOTATE_TASK_BEGIN(taskname);
    Point sub_p;
    sub_p.x = a[i].x * scale_x;
    sub_p.y = a[i].y * scale_y;
    foo(I, sub_p);
    ANNOTATE_TASK_END();
ANNOTATE_SITE_END(site1);
// ...
void foo(int i, Point& sub_p)
{
    b[i].x = b[i].x - sub_p.x;
    b[i].y = b[i].y - sub_p.y;
}