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

Lock Annotations

Lock annotations mark where you expect you will be adding explicit synchronization.

Syntax

C/C++:

ANNOTATE_LOCK_ACQUIRE(pointer-expression); and ANNOTATE_LOCK_RELEASE(pointer-expression);

Fortran:

call annotate_lock_acquire(address) and call annotate_lock_release(address)

C#:

Annotate.LockAcquire([int expr]); and Annotate.LockRelease([int expr]); (for each annotation, its argument is optional)

NOTE:
C# and .NET support is deprecated starting Intel® Advisor 2021.1.

With C/C++ and Fortran programs, all of the lock annotations use an address value to represent distinct locks in your final program. You can use the address value 0 to represent a global "lock" that is the same across the entire program. With C# programs, the argument is an int with a default value of 0 (zero).

Intel recommends that you start by using a default lock, unless you need additional locks for performance scaling.

The modeling step is aware of the standard locking routines in the Windows* OS API, as well as Intel® oneAPI Threading Building Blocks (oneTBB) and OpenMP*, so there is no need to annotate existing locking. Lock annotations are only required for cases where you are not already using synchronization.

The lock-acquire and lock-release annotations denote points in your program where you intend to acquire and release locks. These annotations take a single parameter, which is an address that you choose.

For example, if you decided you would have a lock used only for glob_variable, you specify the same memory address for all cases where you are protecting access to glob_variable, to represent that specific lock. The sample below uses the variable's address to represent the lock that will be associated with glob_variable.

You typically can use one of the following four values, using a finer granularity of synchronization when necessary:

  • The value of 0 (zero) to represent a single unspecified lock that is the same across the entire program.

  • The address of a data structure or other aggregation of data. This represents using a single lock for the collection of data.

  • The address of a member of the data collection. This represents finer-grained locking than the previous value and provides better performance.

  • A variable representing a lock as you move toward final parallel code.

This C/C++ example shows the intent for the parallel program to acquire and release a lock around the access to the global variable glob_variable in each task:

    ...
    extern int glob_variable = 0;
    ...
    ANNOTATE_SITE_BEGIN(sitename);
    for (I=0; i<N; I++) {
        ANNOTATE_TASK_BEGIN(taskfunc1);
        func1(I);
        ANNOTATE_LOCK_ACQUIRE(&glob_variable);
        glob_variable++;
        ANNOTATE_LOCK_RELEASE(&glob_variable);
        func2(I);
        ANNOTATE_TASK_END();
    }
    ANNOTATE_SITE_END();
    ...

This Fortran example also shows the intent to acquire and release a lock around the access to the global variable glob_variable in each task:

 ...
 integer :: glob_variable = 0 

 call annotate_site_begin("sitename")
   do i=1,size
      call annotate_task_begin("taskfunc1")
      call func1(i)
      call annotate_lock_acquire(0)
      glob_variable = glob_variable + 1
      call annotate_lock_release(0)
      call func2(i)
      call annotate_task_end
   end do
 call annotate_site_end
 ...

This C# example also shows the intent to acquire and release a lock around the access to the global variable glob_variable in each task:

 ...
 public int glob_variable {
    get{return nrOfSolutions;}
    set{nrOfSolutions = value;}
 }

 Annotate.SiteBegin("sitename");
 for (int i = 0; i < N; i++) {
	  Annotate.TaskBegin("taskfunc1");	
	  func1(i);
	  Annotate.LockAcquire();
  	glob_variable++;  
  	Annotate.LockRelease();  
	  func2(i);
	  Annotate.TaskEnd();
 }
 Annotate.SiteEnd();
  ...

The following C/C++ example is a typical use of a data item's address. It shows the use of an Entity address, where there is a vector of integers that are each going to have an associated lock, because the program is counting random elements of the array that will be accessed by different tasks, some of which may occasionally have the same random value. The text from adding annotations appears in bold below.

   struct Entity {
       int val;
   };
   ...
   std::vector<Entity> v;
   ...

   for (int I=0; i<v.size()*10000; I++) {
       int random_int = random_n();
       ANNOTATE_LOCK_ACQUIRE(&v[random_int]);
          v[random_int].val++;
       ANNOTATE_LOCK_RELEASE(&v[random_int]);
   }
   ...

Use Lock Annotations

Lock addresses are the basis of lock annotations, and each lock address corresponds to the intent to create a unique lock, or other synchronization mechanism, in the final program. Tasks sharing a parallel site are modeled as executing in parallel unless you describe synchronization using lock addresses, or known locking mechanisms.