有效使用英特尔编译器的卸载特性

有效使用卸载特性

针对英特尔 MIC 架构有效使用英特尔编译器的卸载特性

面向英特尔® MIC 架构的编译器方法

选择编程模式针对英特尔 MIC 架构有效使用英特尔编译器的卸载特性

概述

本文介绍了面向英特尔® MIC 架构的英特尔® Composer XE 2013 异构卸载编程模式的各种最佳方法。

主题

选择要卸载的代码段

基于并行性的选择

选择高度并行化的代码段在协处理器上运行。卸载到协处理器上的串行代码的运行速度比在处理器上的运行速度慢得多。

根据数据流更改卸载的代码段的范围

使用并行化程度作为标准选择要卸载的代码段可能会生成大量小的代码段需要卸载。用户必须考虑在 CPU 和 MIC 之间传输数据的需求。数据交换速度可能较慢(取决于 PCI-E 速度)。由于封送(编译指示卸载)或需要插入 _Cilk_shared 关键字和 _Offload_shared_malloc 动态分配,情况可能会有不同。如果在两个并行段之间执行某些串行处理那么您可以选择 a) 将第一个并行段上的输出数据移动至 CPU在该 CPU 上运行串行代码然后将第二个并行段的输入数据从 CPU 移动至协处理器或者 b) 保留协处理器上的数据并运行串行代码换句话说使代码的整个并行-串行-并行段成为一个卸载单元。

选择数据传输机制

Copyin/Copyout 模式#pragma offload

英特尔 C/C++ 和英特尔 Fortran 编译器都支持这种模式。如果 CPU 和协处理器之间交换的数据仅限于标量或者是比特式可复制元素的数组,请选择 #pragma offload 模式。该模式要求在卸载点对代码进行局部调整,并对函数说明进行标记。Fortran 程序只能使用此模式(Fortran 不支持下面介绍的 Shared-Memory(共享内存)模式)。

Shared-memory 模式 (_Cilk_shared/_Cilk_offload)

该模式只能用于英特尔 C/C++ 编译器Fortran 中不支持。如果 CPU 和协处理器之间交换的数据比简单标量和比特式可复制数组复杂,则可以考虑使用 _Cilk_shared/_Cilk_offload 指令。这些编译指示可帮助实施共享内存卸载编程模式。该模式要求为函数和静态分配的数据提供 _Cilk_shared 属性,并在共享内存中分配动态分配的数据。针对共享内存编程模式实施和使用 _Cilk_Shared/_Cilk_Offload 需要做较多的工作,但是能够使用英特尔 MIC 架构的程序的种类较为丰富,因为它可以处理几乎所有的 C/C++ 程序。

使用 #pragma offload 进行卸载

测量卸载性能

初始化开销

默认情况下当程序执行第一个 #pragma offload 将对所有分配到程序的 MIC 设备进行初始化。初始化包括向每台设备加载 MIC 程序、在 CPU 和设备之间设置数据传输管道,并创建一条 MIC 线程处理来自 CPU 线程的卸载请求。这些活动需要一定的时间。因此,请不要将第一次卸载计入时间测定内。通过向设备执行假卸载来排除该一次性开销。

    // Example of empty offload for initialization purposes

    int main()

    {

        #pragma offload_transfer target(mic)

        ...

    }

或者在开始主程序之前请使用 OFFLOAD_INIT=on_start 环境变量设置对所有可用的 MIC 设备进行预初始化。

卸载数据速率

最大程度地减少输入数据

如果可能请执行局部计算

确保跨卸载的数据持久性

如果后来的卸载需要前一个卸载末尾处的数据值请在协处理器上保留该值。

如果需要在卸载间重复使用数据卸载必须在相同的协处理器上。在目标语句中使用明确的协处理器号可以确保这一点。

持久性静态分配的数据

C/C++ file-scope function-local 变量处声明并具有存储类 “static” 的变量将被静态分配。Fortran 公用块、PROGRAM 块中声明的数据以及具有 “save” 属性的数据被静态分配。只要不被新值覆盖,静态数据在各卸载之间保持相同。使用 nocopy 语句可重复使用之前的值。

     // File scope

    int x, y[100];

    void f()

    {

        X = 55;

        // x sent from CPU, y computed on coprocessor

        ...

    #pragma offload target(mic:0) in(x) nocopy(y)

    { y[50] = 66; }

    …

    #pragma offload target(mic:0) nocopy(x,y)

    { // x and y retain previous values }

    }

持久性堆栈分配的数据

C/C++ Fortran 默认情况下为在函数以及子例程中声明的变量指定 “automatic” 或堆栈存储。最大程度地减少在各个卸载之间保留 function-local 值的需求。

在卸载环境中每个卸载的区域在协处理器上运行一个单独的函数。堆栈分配的变量通常不会在卸载之间保留。为了跨卸载实施数据持久性功能,如果请求 “nocopy”,那么标量值在卸载末端被复制到 CPU,并在执行下一个卸载时再次复制到协处理器,以便模拟各卸载之间的保留。出于效率方面的原因,不建议非标量(即大型 function-local 数组和结构对象)使用此方法。从版本 13.0.0. 079 开始(不是更早的 Beta 版),编译器从功能上便支持此类 function-local 数组的卸载。但是,对于性能较为敏感的部分,我们不建议使用此特性。

  void f()

  {

        int x = 55;

        int y[10] = { 0,1,2,3,4,5,6,7,8,9};

        // x, y sent from CPU

        // To use values computed into y by this offload in next offload,

    // y is brought back to the CPU

    #pragma offload target(mic:0) in(x,y) inout(y)

    { y[5] = 66; }

    // The assignment to x on the CPU

    // is independent of the value of x on the coprocessor

    x = 30;

    …

    // Reuse of x from previous offload is possible using nocopy

    // However, array y needs to be sent again from CPU

    #pragma offload target(mic:0) nocopy(x) in(y)

    {   = y[5]; // Has value 66

        = x;    // x has value 55 from first offload

    }

  }   

持久性Heap分配的数据。

协处理器堆在各卸载之间是一致的。您可以通过两种方法在 MIC 上使用堆内存

1.     使用 #pragma offload

2.     在协处理器上明确地调用 malloc

或者使用 #pragma 让编译器管理动态内存或者使用 malloc/free 对其进行管理。使用 alloc_if free_if 对编译器管理的动态内存执行分配/取消分配操作。

编译器管理、堆分配的数据

内存分配由 alloc_if free_if 控制数据传输由 in/out/inout/nocopy 控制。它们是相互独立的但是数据仅能在分配内存上传输。
// The following macros are use in all the samples when alloc_if/free_if clauses are used#define ALLOC alloc_if(1) free_if(0)
#define FREE alloc_if(0) free_if(1)
#define REUSE alloc_if(0) free_if(0)

void f()
{
  int *p = (int *)malloc(100*sizeof(int));
  // Memory is allocated for p, data is sent from CPU and retained
  #pragma offload target(mic:0) in(p[0:100] :
ALLOC)
  { p[6]] = 66; }
  …
  // Memory for p reused from previous offload and retained once again
  // Fresh data is sent into the memory
  #pragma offload target(mic:0) in(p[0:100] : REUSE)
  { p[6]] = 66; }
  …
  // Memory for p reused from previous offload,
  // freed after this offload.
  // Final data is pulled from coprocessor to CPU
  #pragma offload target(mic:0) out(p[0:100] : FREE)
  { p[7]] = 77; }
  …
}

明确管理的堆分配数据

协处理器上运行的代码可以调用 malloc/free 以便明确地分配/取消分配动态内存。指向以这种方式分配的动态内存的指针变量是标量,并且取决于上面介绍的数据持久性规则及其定义范围 静态或 function-local
为了防止编译器管理的动态分配和明确的动态分配之间互相干扰,请针对(在明确管理的卸载区域内参考的)指针变量使用 nocopy 语句。

void f()
{
  int *p;
  …
  // The nocopy clause ensures CPU values pointed to by p
  // are not transferred to coprocessor
  #pragma offload target(mic:0) nocopy(p)
  {
    // Allocate dynamic memory for p on coprocessor
    P = (int *)malloc(100);
    P[0] = 77;
    …
  }
  ..

  // The nocopy clause ensures p is not altered by the offload process
  #pragma offload target(mic:0) nocopy(p)
  {
    // Reuse dynamic memory pointed to by p
    … = p[0]; // Will be 77
  }

}

 

局部指针与跨卸载使用的指针

卸载区域内使用的指针在默认情况下为 inout即与其相关的数据可以内外传输。有时候,指针可能仅限于局部使用,也就是说,只能在协处理器上分配和使用。nocopy 语句在这种情况下十分有用它可以使卸载语句不对指针进行修改并支持编程人员明确地管理指针的值。在其他情况下,数据从 CPU 传输至指针,后续卸载可能 a) 使用分配的相同内存并向其传输新数据,或者b) 保留相同的内存,重复使用相同的数据。对于 a),可以使用具有与元素数量相同长度的语句。对于 b),可以使用长度是 0 的语句以便“刷新”指针,但这会阻止数据传输。

in/out/nocopy 的完整描述以及长度语句的使用

 

长度或元素数量

< 0

长度或元素数量

== 0

长度或元素数量

> 0

nocopy :

  alloc_if(0) free_if(0)

OK可用于局部 MIC ptr

OK可用于局部 MIC ptr

OK可用于局部 MIC ptr

nocopy :

  alloc_if(0) free_if(1)

OK,更新 ptr,释放内存(忽略长度)

OK,更新 ptr,释放内存(忽略长度)

OK,更新 ptr,释放内存(忽略长度)

nocopy :

  alloc_if(1) free_if(0)

错误,不能 alloc <0

错误,不能 alloc 0

OK,执行分配,更新 ptr

nocopy :

  alloc_if(1) free_if(1)

错误,不能 alloc <0

错误,不能 alloc 0

OK,执行分配,更新 ptr,释放内存

in / out / inout:

  alloc_if(0) free_if(0)

OK,仅更新 ptr

OK,仅更新 ptr

OK,仅更新 ptr,传输

in / out / inout:

  alloc_if(0) free_if(1)

OK,更新 ptr,无传输,释放

OK,更新 ptr,无传输,释放

OK,更新 ptr,传输,释放

in / out / inout:

  alloc_if(1) free_if(0)

错误,不能 alloc/transfer <0

错误,不能 alloc 0

OK,分配,更新 ptr,传输

in / out / inout:

  alloc_if(1) free_if(1)

错误,不能 alloc/transfer <0

错误,不能 alloc 0

OK,分配,更新 ptr,传输,释放

 

 

 

 

 

 

 

 

 

 

 

 

in/out/nocopy 以及长度语句的使用示例如下
局部指针示例

int *p;
int *temp;

p = malloc(SIZE);
temp = p;

// transfer data into “p”, but do nothing with “temp”
#pragma offload target(mic:0) in(p :
free_if(0)) nocopy(temp)
{
   // temp is allocated locally
    temp = malloc(…);
    memcpy(temp, p, …);
}

// Reuse temp’s value from previous offload
#pragma offload target(mic:0) out(p : alloc_if(0)) nocopy(temp)
{
   // temp’s value is preserved from the previous offload
    memcpy(p, temp, …);
    free(temp);
}


持久性 MIC 指针和选择性数据传输示例

#include <stdlib.h>
#include <stdio.h>

#define SIZE 10

void func1(int *p)
{
   int i;

   // Because transfer count is 0, no data is transferred to MIC
   // However, because "in" is used,
   // the pointer value gets initialized on MIC
   #pragma offload target(mic:0) \
        in(p :
length(0) alloc_if(0) free_if(0))
   { for(i=0; i<SIZE; i++) printf("%3d", p[i]); printf("\n"); }
}

int main()
{
   int i;
   int *a;

   a = (int *)malloc(SIZE*sizeof(int));
   for(i=0; i<SIZE; i++) a[i] = i;

   // Allocate a on MIC only; transfer no data
   #pragma offload_transfer target(mic:0) \
       nocopy(a : length(SIZE) alloc_if(1) free_if(0))

   // Pick up the memory allocated for a and transfer data into it
   // Transfer count of SIZE is used
   // Each element of a is printed, then incremented
   #pragma offload target(mic) \
       in(a : length(SIZE) alloc_if(0) free_if(0))
   { for(i=0; i<SIZE; i++) printf("%3d", a[i]++); printf("\n"); }

   func1(a);

   // Fetch data back to CPU, and free a on MIC
   #pragma offload_transfer target(mic:0) \
       out(a : length(SIZE) alloc_if(0) free_if(1))

   return 0;
}

CPU MIC 之间传输非比特式可复制数据

有时候包含比特式可复制元素例如标量和数组和非比特式元素例如指向其它数据的指针的数据需要在 CPU MIC 之间交换。默认情况下,编译器不允许 in/out 语句中的此类对象。如果程序只是涉及传输此类对象的比特式可复制元素,那么编译器可使用 -wd<number> 开关来禁止此错误,或者使用 -ww<number> 开关将错误转化为告警。

注:

1.     非比特式可复制元素具有不确定的值在首次为其分配有效值之前请不要访问这些字段。

2.    也可能会存在其它情况例如编译器发出 "not bitwise copyable" 诊断。错误可能被覆盖错误代码会打印出来。在 -wd -ww 开关中使用该错误代码。

    // Example of Non-Bitwise Object Transfer, Only Bitwise Copyable Data Needs to be Transferred

    --- file wd2568.cpp ---

    #include <complex>

    typedef std::complex<float> Value;

 

    void f()

    {

        const Value* C;

        #pragma offload_transfer target(mic) in(C:length(2))

    }

   

    > icc -c wd2568.cpp

    wd2568.cpp(8): error #2568: variable "C" used in this offload region is not bitwise copyable

    #pragma offload target(mic) in(C:length(2))

    ^

    compilation aborted for wd2563.cpp (code 2)

   

    // Compiling with -wd2568 allows the compilation to proceed

    > icc -c wd2568.cpp -wd2568

    > 

在其它情况下需要传输非比特式对象的所有元素。此时,您必须将对象“分解”为多个部分并单独进行传输,之后在 MIC 上重新组合。

    // Example of Non-Bitwise Object Transfer, All Data Elements Needed

    typedef struct {

        int m1;

        char *m2;

    } nbwcs;

   

    void sample11()

    {

        nbwcs struct1;

        struct1.m1 = 10;

        struct1.m2 = malloc(11);

        int m1;

        char *m2;

       

        // Disassemble the struct for transfer to target

        m1 = struct1.m1;

        m2 = struct1.m2;

       

        #pragma offload target(mic) inout(m1) inout(m2 : length(11)

        {

            nbwcs struct2;

            // Reassemble the struct on the target

            struct2.m1 = m1;

            struct2.m2 = m2;

            ...

        }

        ...

    }

       

 

最大程度降低协处理器内存分配开销


协处理器上的动态内存分配速度可能比较慢。通过减少分配和释放操作可最大程度地降低分配/取消分配开销。如果一个数组要在 CPU 和协处理器之间多次传输,请在第一次使用时分配并在最后一次使用时释放。请查看数据持久性堆分配的数据下的示例。尽管不能在卸载代码中重复使用相同的数组,但是在 MIC 上分配的相同的内存块可以重复使用。

 

// Allocate a buffer of “count” elements on coprocessor,r />// accessible through the CPU value of p
#pragma offload_transfer target(mic:0) nocopy(p[0:count] :
ALLOC)

// CPU data from x is transferred into coprocessor buffer p
// elements1 <= count
#pragma offload target(mic:0) \
        in(x[0:elements1] : REUSE into(p[0:elements1])
{ …  = p[10]; }

// CPU data from y is transferred into coprocessor buffer p
// elements2 <= count
#pragma offload target(mic:0) \
        in(y[0:elements2] : REUSE into(p[0:elements2])
{  … = p[10]; }

// Free the buffer
#pragma offload_transfer target(mic:0) nocopy(p[0:count] : FREE)

请注意内存缓冲会继续分配即便不需要),这将消耗可用的协处理器内存因此在最大程度地降低分配/取消分配开销时需要兼顾内存方面的需求。

卸载数据对齐

要在协处理器上实现代码矢量化请在 64B 或更高的边界上对齐数据。对于静态分配的变量,可使用 __declspec(align(64)) 实现这一点。对于传输至协处理器的指针数据,可使用 #pragma offload 的对齐修改器(modifier)。
#pragma offload target(mic) in(p[0:2048] :align(64))

请注意卸载库在 64B 中为协处理器数据分配的补偿通常与 CPU 数据的 64B 中的补偿相同。该补偿匹配可确保 CPU 和协处理器之间的快速 DMA 传输。对齐修改器可覆盖此补偿匹配。为了加快 DMA 数据传输速度以及正确对齐协处理器数据,请对齐 CPU 数据,并且不要明确使用对齐指令。

最大程度地提高数据传输速率

CPU 和协处理器之间的数据传输速度对于堆栈数据是最慢的对于静态和动态分配的数据是最快的。在 64B 或更高的边界上对齐 CPU 数据有助于提高数据传输速度。在 2MB 处对齐可实现最高的传输速度。使数据传输大小成为 64B 的倍数有助于提高传输速度,成为 2MB 的倍数可实现最高的传输速度。通常,数据传输规模越大,带宽也就越高。在更大的 (2MB) 页面分配协处理器内存可提高数据传输速度。请参考使用环境变量 MIC_USE_2MB_BUFFERS的说明。

重叠数据传输和卸载计算

卸载计算需要的输入数据可在卸载之前发送。CPU 可在数据传输时继续处理。在下面的示例中,f1 f2 在要使用它们的值的卸载之前发送至协处理器。

01   const int N = 4086;
02   float *f1, *f2;
03   float result;
04   f1 = (float *)memalign(64, N*sizeof(float));
05   f2 = (float *)memalign(64, N*sizeof(float));
...

10   // CPU issues send and continues
11   #pragma offload_transfer in( f1[0:N] ) signal(f1)
12  
...

20   // CPU issues send and continues
21   #pragma offload_transfer in( f2[0:N] ) signal(f2)
22  
...
30   // CPU issues request to do computation using f1 and f2
31   // Coprocessor begins execution after pre-sent data is received
32   #pragma offload wait(f1, f2) out( result )
33   {
34          result = foo(N, f1, f2);
35   }

要实现从 MIC CPU 的异步数据接收可将信号和等待用作两个不同编译指示的语句。第一个卸载执行计算,但是仅发起数据传输。第二个编译指示为要完成的数据传输造成等待。

01   const int N = 4086;
02   float *f1, *f2;
03   f1 = (float *)memalign(64, N*sizeof(float));
04   f2 = (float *)memalign(64, N*sizeof(float));
...


10   // CPU sends f1 as input synchronously
11   // The output is in f2, but is not needed immediately
12   #pragma offload in(f1[0:N]) nocopy(f2[0:N]) signal(f2)
14   {
15        foo(N, f1, f2);
16   }
..
20   #pragma offload_transfer wait(f2) out(f2[0:N])
21  
22   // CPU can now use the result in f2
23   ...

卸载 Lambda 函数

lambda 是一种内联函数。如果必须卸载该函数那么向其卸载属性具有一定的难度。例如

#pragma offload_attribute(push,target(mic))
#include <stdio.h>

template<typename F>
void Run( F f ) {
    f();
}
#pragma offload_attribute(pop)

int main() {
#pragma offload target(mic)
    Run( [&] () __attribute__((target(mic)))
{
#ifdef __MIC__
        printf("MIC says Hello, world\n");
#else
        printf("CPU says Hello, world\n");
#endif
    } );
}

使用 _Cilk_shared/_Cilk_offload 进行卸载 

标记数据和 Classes _Cilk_shared

共享指针声明

共享指针声明如下

_Cilk_shared int *p;

指针到共享数据的声明

到共享数据的指针通过这种方式编写。

int * _Cilk_shared q;

到共享数据的共享指针是这两者的组合。

_Cilk_shared int * _Cilk_shared r;

Class Type类类型声明为 Shared共享

如果必须将类类型声明为共享请在和其余部分之间使用关键字 “class”。将 _Cilk_shared 至于起始位置对要声明为共享(非类型)的数据进行标记:

Class _Cilk_shared C {

  // class members

 };

为共享数据分配动态内存

动态分配的共享数据必须从共享内存池分配。可通过使用 API 实现这一点:

_Offload_shared_malloc

_Offload_shared_free

_Offload_shared_aligned_malloc

_Offload_shared_aligned_free

使用 Placement New

有时候用户不能直接控制内存分配例如 STL 对象。offload.h 头提供了一种 placement new 方法以便将 STL 内存分配转至共享内存。例如

#include <vector>
#include <stdio.h>
using namespace std;

#include "offload.h"

_Cilk_shared vector<int, __offload::shared_allocator<int> > * _Cilk_shared v;

_Cilk_shared void foo() {
   for (int i = 0; i<  5; i++) {
     printf("%d\n", (*v)[i]);  // fault here otherwise
   }
}

int main() {
  // v is allocated in shared mem
  // v's elements are also in shared mem
  v = new (_Offload_shared_malloc(sizeof(vector<int>)))
          _Cilk_shared
          vector<int, __offload::shared_allocator<int> >(5); 
                               
  for (int i = 0; i<  5; i++) {
    (*v)[i] = i;
  }
  _Cilk_offload foo();
  return 0;
}

提高 _Cilk_offload/_Cilk_shared 的性能

共享数据的默认内存模式是假设 CPU 和协处理器可修改要共享的数据。如果应用数据模式如下,卸载的输入数据仅从 CPU 发送至协处理器,卸载完成后,所有修改的数据再发送至 CPU,而且无需与其它共享(在 CPU 上同时修改的)数据融合,然后可指定更加简单和高效的同步模式。该模式可通过使用环境变量 MYO_CONSISTENCE_PROTOCOL 实现。

示例

setenv MYO_CONSISTENCE_PROTOCOL HYBRID_UPDATE_NOT_SHARED

将卸载代码与协处理器库链接

目前编译器在检查共享与非共享指针混合方面非常严格因此有必要绕开其中某些检查。通常情况下,只要使用 cast,仅知道共享的例程便可处理共享数据。
按照下面的步骤操作,将面向 MIC 的第三方库与卸载代码链接:
1.
使第三方库保持不变即使用 –mmic 构建并且对共享不了解。
2.
从 CPU 程序卸载到 MIC 上的某些函数(作为数据交换函数使用)。这些函数将标记为 _Cilk_shared,并将处理标记为 _Cilk_shared 的数据。
3.
通过这些在 MIC 上运行的数据交换函数可以调用仅涉及 MIC 的库。现在,由于在标记为 _Cilk_shared 的函数中参考的数据和函数要求是 _Cilk_shared,您需要选择在外部库(不是使用 Shared 关键字构建)中定义的数据和函数。
CPU 代码 à SHARED 使用 –mmic MIC à (cast) 代码上运行

示例

 --- Makefile ---

 main.out:         main.cpp libbar.so

             icc -o main.out main.cpp -offload-option,mic,compiler,"-L. -lbar"

 

 libbar.so:        libbar.cpp

            icc -mmic -fPIC -shared -o libbar.so libbar.cpp

 

 clean:

           rm *.o *.so main.out

 

run:

          export MIC_LD_LIBRARY_PATH=".:$$MIC_LD_LIBRARY_PATH"

          main.out
--- file main.cpp ---

#include <vector>
#include <stdio.h>
using namespace std;

#include "offload.h"

typedef vector<float, __offload::shared_allocator<float> > _Cilk_shared * SHARED_VECTOR_PTR;
typedef vector<float> * VECTOR_PTR;
SHARED_VECTOR_PTR v;
typedef _Cilk_shared void (*SHARED_FUNC)(VECTOR_PTR v);

extern void libbar(VECTOR_PTR v);

_Cilk_shared void bar(VECTOR_PTR v)
{
        if (_Offload_get_device_number() == 0)
        {
                printf("MIC bar\n");
                for (int i = 0; i<  5; i++) {
                        printf("\t%f\n", (*v)[i]);
                }
        }
}

_Cilk_shared void foo(SHARED_VECTOR_PTR v)
{
        if (_Offload_get_device_number() == 0)
        {
                printf("MIC foo\n");
                for (int i = 0; i<  5; i++) {
                        printf("\t%f\n", (*v)[i]);
                }
        }
        bar((VECTOR_PTR)v);
#ifdef __MIC__
   (*(SHARED_FUNC)&libbar)((VECTOR_PTR)v);
#else
#endif
}

int main()
{
        // v's elements are in shared mem
        v = new (_Offload_shared_malloc(sizeof(vector<float>)))
                _Cilk_shared vector<float, __offload::shared_allocator<float> > (5);


        for (int i = 0; i<  5; i++) {
                (*v)[i] = i;
        }
        _Cilk_offload foo(v);

        return 0;
}


 

 ---- file libbar.cpp ---

#include <vector>
#include <stdio.h>
using namespace std;

typedef vector<float> * VECTOR_PTR;

void libbar(VECTOR_PTR v)
{
        if (_Offload_get_device_number() == 0)
        {
                printf("MIC libbar\n");
                for (int i = 0; i<  5; i++) {
                        printf("\t%f\n", (*v)[i]);
                }
        }
}

CPU MIC 定制代码

避免 #ifdef __MIC__

有时候卸载代码需要面向协处理器的定制。如果可能请使用动态检查选择要在协处理器上运行的代码。

   if (_Offload_get_device_number() >= 0)

   {

       // MIC version

   } else {

      // CPU version

   }

如果 MIC CPU 版本使用处理器上没有的内联函数那么该定制化方法可能不可行。在这种情况下可使用 #ifdef。但是,请不要在 #pragma offload 结构中直接使用 #ifdef __MIC__,因为它会在协处理器发送/接收以及 CPU 发送/接收的变量之前造成不匹配。出现不匹配的原因是默认变量是 inout,而且两个代码版本上的变量参考可能不相同。

 #ifdef __MIC__

    // MIC version

 #else

   // CPU version

 #endif

控制传输至 CPU MIC 编译器的选项

用户可见、记录的选项

示例

// The following compiler invocation produces the output shown below.

   ifort -openmp program.f90 -g -o g.out -watch=mic_cmd

MIC command line:

   ifort -openmp program.f90 -g -o g.out

内部、未记录的选项

–m 开始的内部编译器选项通常不会从 CPU 自动传输至 MIC 编译器。必须明确地为 CPU 或 MIC 编译指定这些选项(使用 –offload-option,mic,compiler CPU 选项)。
示例

// The following passes an internal option to the CPU compiler
// The MIC command line is asked to be printed
  icc -c test.c -watch=mic_cmd -mP2OPT_il0_list=1
MIC command line:

icc -c test.c

// The following passes an internal option to the MIC compiler
// The MIC command line is asked to be printed
  icc -c test.c -watch=mic_cmd -offload-option,mic,compiler,-mP2OPT_il0_list=1
MIC command line:
icc -c test.c -mP2OPT_il0_list=1

用于控制卸载的环境变量

环境变量有两类:

1.     影响卸载运行时库运行方式的变量

2.     由卸载库传输至协处理器执行环境的变量。

我们首先在第一类中描述环境变量方式是加上 "MIC_" "OFFLOAD_" 前缀。前缀是固定的,正如环境变量名称所示。

特殊环境变量 MIC_ENV_PREFIX 用于与第二类中的变量区分。我们将在本部分的结尾处加以介绍。

MIC_USE_2MB_BUFFERS

设置阈值以创建具有较大页面的缓冲。如果其值超过阈值便会创建具有较大页面的缓冲。

示例

// any variable allocated on MIC that is equal to or greater than

// 100KB in size will be allocated in large pages.

setenv MIC_USE_2MB_BUFFERS 100k

MIC_STACKSIZE

MIC 上设置卸载进程的大小。这是堆栈的总容量。使用 MIC_OMP_STACKSIZE 修改每个 OpenMP 线程的大小。

示例

setenv MIC_STACKSIZE 100M // Sets MIC stack to 100 MB

MIC_LD_LIBRARY_PATH

设置 MIC 卸载代码需要的共享库所在的路径。

参考将卸载代码与协处理器库链接部分的示例。

 

OFFLOAD_REPORT

__Offload_report(int on_or_off); // to be called on CPU only 

API 允许您在运行时打开/关闭报告环境变量 OFFLOAD_REPORT 被设置为 1 2

API 在运行时更改标记的值。这不会影响环境变量的设置。它会影响哪些卸载生成报告。

示例

#include <stdio.h>

__declspec(target(mic)) volatile int x;

int main()

{

    __Offload_report(0);

    #pragma offload target(mic)

    {

        x = 1;

    }

    __Offload_report(1);

    #pragma offload target(mic)

    {

        x = 2;

    }

    return 0;

}

对于上面的程序来说当设置为 OFFLOAD_REPORT=1 报告显示如下

[Offload] [MIC 0] [File] test_ofld0.c

[Offload] [MIC 0] [Line] 15

[Offload] [MIC 0] [CPU Time] 0.000268 (seconds)

[Offload] [MIC 0] [MIC Time] 0.000022 (seconds)

 

如果设置是 OFFLOAD_REPORT=2报告显示如下

[Offload] [MIC 0] [File] test_ofld0.c

[Offload] [MIC 0] [Line] 15

[Offload] [MIC 0] [CPU Time] 0.000263 (seconds)

[Offload] [MIC 0] [CPU->MIC Data] 0 (bytes)

[Offload] [MIC 0] [MIC Time] 0.000023 (seconds)

[Offload] [MIC 0] [MIC->CPU Data] 4 (bytes)

[CPU->MIC Data] [MIC->CPU Data] 是传输的总数据(字节)

 

OFFLOAD_DEVICES

环境变量 OFFLOAD_DEVICES 限制进程仅使用被指定为变量值的 MIC 卡。<value> 是逗号分隔的物理设备编号列表范围从 0 (number_of_devices_in_the_system-1)

用于卸载的设备以逻辑顺序进行编号。即 _Offload_number_of_devices() 返回允许设备的编号在卸载编译指示的目标指定语句中指定的设备索引的范围是从 0 (number_of_allowed_devices-1)

示例

setenv OFFLOAD_DEVICES “1,2”

允许程序仅使用物理 MIC 1 2例如在安装了 4 块卡的系统中。针对设备 0 1 的卸载将在物理设备 1 2 上执行。目标编号超过 1 的卸载将采用环绕式处理(wrap-around),以便在逻辑设备 0 1(可映射到物理卡 1 2)中保留所有卸载。当在物理设备 1 2 上运行卸载时,在 MIC 设备上执行的函数 _Offload_get_device_number() 将返回 0 1

OFFLOAD_INIT

要初始化 MIC 设备时环境变量指定卸载运行时的线索。

支持的值

on_start

所有设备在输入主值之前初始化。

on_offload

设备初始化在第一次卸载之前进行。初始化仅在处理卸载的 MIC 设备上进行。

on_offload_all

所有可用的 MIC 设备在第一次卸载之前进行初始化。

默认为 on_offload_all为了相互兼容

 

MIC_ENV_PREFIX

这是向在 MIC 卡上运行的进程传输环境变量值的一般方法。

该环境变量的设置不会影响本章之前讨论的固定 MIC_* 环境变量 MIC_USE_2MB_BUFFERSMIC_STACKSIZE MIC_LD_LIBRARY_PATH。这些名称是固定的。

默认情况下当进行卸载时在执行 CPU 程序的环境中定义的变量被复制到协处理器的执行环境。您可以定义环境变量 MIC_ENV_PREFIX 来修改此行为。当您设置 MIC_ENV_PREFIX 时,并不是所有 CPU 环境变量被复制到协处理器,而是只有以 MIC_ENV_PREFIX 环境变量值开始的环境变量才进行复制。协处理器上设置的环境变量已经删除前缀值。因此,您可以独立控制OpenMP*、英特尔® Cilk™ Plus,以及其它使用通用环境变量名称的执行环境。

因此如果没有设置 MIC_ENV_PREFIX卸载运行时只是将主机环境复制到协处理器。如果设置 MIC_ENV_PREFIX,只有以 MIC_ENV_PREFIX 定义的值开头的环境变量名称才被传输至目标(删除前缀)。

因此MIC_ENV_PREFIX 被设置为前缀的值该前缀用于识别环境变量值面向在 MIC 设备上运行的程序。例如,setenv MIC_ENV_PREFIX MYCARDS 使用 “MYCARDS” 作为字符串,以说明环境变量用于 MIC 进程。

格式 <mic-prefix>_<var>=<value> 的环境变量值将向每块卡发送 <var>=<value>

格式 <mic-prefix>_<card-number>_<var>=<value> 的环境变量值将向编号为 <card-number> MIC 卡发送 <var>=<value>

格式 <mic-prefix>_ENV=<variable1=value1|variable2=value2> 的环境变量值将向每块卡发送 <variable1>=<value1> <variable2>=<value2>

格式 <mic-prefix>_<card-number>_ENV=<variable1=value1|variable2=value2> 的环境变量值将向编号为 <card-number> MIC 卡发送 <variable1>=<value1> <variable2>=<value2>

示例

setenv MIC_ENV_PREFIX PHI // Defines the prefix to be used

setenv PHI_ABCD abcd               // Sets ABCD=abcd on all cards setenv PHI_2_EFGH efgh                 // Sets EFGH=efgh on logical MIC 2 setenv PHI_VAR X=x|Y=y                // Sets X=x and Y=y on all cards setenv PHI_4_VAR P=p|Q=q // Sets P=p and Q=q on MIC 4

 

下一步

要在英特尔® 至强融核协处理器上成功调试您的应用请务必通读此指南并点击文中的超链接查看相关内容。本指南提供了实现最佳应用性能所要执行的步骤。

返回到? HYPERLINK "http://software.intel.com/en-us/articles/native-and-offload-programming-models" \l "next_steps" 本机和卸载编程模式原生和卸载编程模式

 

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.