インテル® C++ コンパイラー 17.0 デベロッパー・ガイドおよびリファレンス

offload_transfer

同期データ転送を開始して完了します。 signal 節と一緒に使用すると、非同期データ転送を開始します。 このプラグマは、インテル® MIC アーキテクチャーにのみ適用されます。

構文

#pragma offload_transfer clause[clause...]

必須の指示節

オプション指示節

引数

必須の指示節

offload-parameter

ホストとターゲット間でコピーされるプログラム変数とデータの量を制御します。 clause (節) は次のいずれかです。

in

ターゲット領域への入力に使用する変数。この値はターゲット領域が完了した後にターゲットからホストにコピーされません。

構文: in ( variable-ref [, variable-ref …] [ modifier[ , modifier… ] ] )

out

ターゲット領域の出力に使用する変数。ホストはこの変数をターゲットにコピーしません。

構文: out ( variable-ref [, variable-ref …] [ modifier[ , modifier… ] ]

)

nocopy

以前のターゲットの実行から再利用される値、またはオフロードされたコードセクション内で使用される値を含む変数は、コピーを防ぐために nocopy 節にリストします。

構文: nocopy ( variable-ref [, variable-ref …] [ modifier[ , modifier… ] ] )

in または out 式 (後述の modifier の説明を参照) は、式が記述される文または節の前で評価されます。

宣言からサイズが分かる配列変数は全体でコピーされます。 配列のサブセットを処理する場合、サブセットの開始要素へのポインターと element-count-expr を使用して配列のサブセットを転送します。

この引数の変数は次のとおりです。

variable-ref

次のいずれかです。

  • C/C++ 識別子。

  • variable-ref 識別子。

    この引数の変数には次の構文を使用します。

    • var : length ( l )

    • var [ 0 : length ]

  • array-slice

    1 つの連続する配列要素のセットを表す配列式です。

modifier

次のいずれかです。

  • align (expression)

    align-expression は、スカラー整数式または配列スライス式です。 次のものと一緒に使用します。

    • ポインター変数。

      align-expression は、スカラー整数式でなければなりません。 ターゲットで割り当てるポインターデータの最小アライメントを指定します。 値は 2 の累乗でなければなりません。

    • ポインター配列。

      align-expression は、スカラー整数式または配列スライス式のいずれかです。 ターゲットで割り当てるポインターデータの最小アライメントを指定します。 値は 2 の累乗でなければなりません。

      align-expression が配列スライス式の場合は、ポインター配列の各ポインターに適用されるアライメント値のセットを指定します。 1 つ目のアライメント値は 1 つ目のポインターに、2 つ目のアライメント値は 2 つ目のポインターに、というように適用されます。

  • alloc (array-slice)

    array-slice は、割り当てが必要な配列要素のセットを指定します。 in/ out 式で指定されたデータは、ターゲット上に割り当てられた配列の対応する部分に転送されます。

    この修飾子の引数には次の構文を使用します。

    • var[start : length]

    詳細は、「部分配列の一部へのメモリー割り当て」を参照してください。

  • alloc_if ( condition )

    condition は、スカラーブール式またはブール配列スライス式です。 次のものと一緒に使用します。

    • ポインター変数。

      condition は、スカラーブール式でなければなりません。 in/out/inout/nocopy 節の変数に割り当てられているメモリーを割り当てるかどうかを制御します。 式が TRUE と評価されると、節でリストされている各変数に新しいメモリーが割り当てられます。 条件が FALSE と評価されると、ターゲットにすでに割り当てられている値が再利用されます (データ保持)。 前のオフロードに対して free_if (0) 節を使用し、十分なサイズのメモリーブロックがターゲットの変数に割り当てられていることを確認する必要があります。

    • ポインター配列。

      condition は、ブール式またはブール配列スライス式です。 ターゲット上で in/out/inout/nocopy 節の変数にメモリーを割り当てるかどうかを制御します。

      condition が配列スライス式の場合は、ポインター配列の各ポインターに適用されるブール値のセットを指定します。 1 つ目のブール値は 1 つ目のポインターに、2 つ目のブール値は 2 つ目のポインターに、というように適用されます。

    この修飾子のデフォルト設定は次のとおりです。

    修飾子

    デフォルト設定

    in

    true

    inout

    true

    out

    true

    nocopy

    false

  • alloc_extent ( start : length )

    startlength は、整数値のセットとなる整数式または配列スライス式のいずれかです。 どちらの式も実行時に計算されます。 この修飾子は、次のものと一緒に使用します。

    • ポインター配列。

      startlength は、整数式または配列スライス式です。 整数式の場合は、各ポインターに適用されます。 配列スライスの場合は、ポインター配列の各要素に対応する配列スライスの要素が指定されます。

      start は、ターゲットで割り当てる最初の要素を指定します。 length は、割り当てる要素の数を指定します。 start が負の場合、ランタイムエラーが発生します。

  • extent ( start : length )

    startlength は、整数値のセットとなる整数式または配列スライス式のいずれかです。 どちらの式も実行時に計算されます。 この修飾子は、次のものと一緒に使用します。

    • ポインター配列。

      ホストとターゲットのメモリーアドレスは対応していないため、配列内のポインターの値はホスト/ターゲット間でコピーされません。 代わりに、ポインターが指すオブジェクトがターゲットに、またはターゲットからコピーされ、ポインター配列が更新されます。

      startlength は、整数式または配列スライス式です。 整数式の場合は、各ポインターに適用されます。 配列スライスの場合は、ポインター配列の各要素に対応する配列スライスの要素が指定されます。

      start は、転送する最初の要素を指定します。 length は、転送する要素の数を指定します。 start が負の場合、ランタイムエラーが発生します。

  • free_if (condition)

    condition は、スカラーブール式またはブール配列スライス式です。 次のものと一緒に使用します。

    • ポインター変数。

      condition は、スカラーブール式でなければなりません。in/out/inout/nocopy 節の変数に割り当てられているメモリーを解放するかどうかを制御します。 式が false と評価されると、リストの各変数に割り当てられたメモリーに対する操作は行われません。 割り当てられたメモリーは後の節で再利用できます (データ保持)。

    • ポインター配列。

      condition は、ブール式またはブール配列スライス式です。 ターゲット上で in/out/inout/nocopy 節の変数のメモリーを解放するかどうかを制御します。

      condition が配列スライス式の場合は、ポインター配列の各ポインターに適用されるブール値のセットを指定します。 1 つ目のブール値は 1 つ目のポインターに、2 つ目のブール値は 2 つ目のポインターに、というように適用されます。

    この修飾子のデフォルト設定は次のとおりです。

    修飾子

    デフォルト設定

    in

    true

    inout

    true

    out

    true

    nocopy

    false

    詳細は、「ポインター変数のメモリー割り当ての管理」を参照してください。

  • into (var-exp)

    var-exp は変数式です。

    この修飾子は、ホスト上の 1 つの変数からターゲット上の別の変数へ (またはその逆に) データを転送します。 この修飾子を使用する場合、variable-ref では 1 つの項目のみ指定できます。

    この節の引数には次の構文を使用します。

    • var (length 修飾子が一緒に使用される場合)

    • var [ start : length ] (個別の length 修飾子が使用されない場合)

    詳細は、「1 つの変数から別の変数へのデータの移動」を参照してください。

  • into_extent ( start : length )

    startlength は、整数値のセットとなる整数式または配列スライス式のいずれかです。 どちらの式も実行時に計算されます。 この修飾子は、次のものと一緒に使用します。

    • into 修飾子と一緒に使用されるポインター配列。

      startlength は、整数式または配列スライス式です。 整数式の場合は、各ポインターに適用されます。 配列スライスの場合は、ポインター配列の各要素に対応する配列スライスの要素が指定されます。

      start は、ターゲットで割り当てる最初の要素を指定します。 length は、割り当てる要素の数を指定します。 start が負の場合、ランタイムエラーが発生します。

  • length (element-count-expr)

    element-count-expr は、スカラー整数式または配列スライス式で、実行時に計算されます。 次のものと一緒に使用します。

    • ポインター変数。

      ホストとターゲットのメモリーアドレスが対応していないため、ポインター変数の値はホスト/ターゲットのインターフェイス間でコピーされません。 代わりに、ポインターが指すオブジェクトがターゲットに、またはターゲットからコピーされ、ポインター変数の値が更新されます。

      スカラー整数式 element-count-expr を使用して、ポインターが指すデータとして見なされるポインター型の要素の数を指定できます。 式の値が 0 または負の場合、ランタイムエラーが発生します。スカラーポインター変数では、配列スライス式は指定できません。

    • 可変長配列。

      element-count-expr は、ホストとターゲット間でコピーする要素の数を指定します。

    • ポインター配列。

      element-count-expr としてスカラー整数式で、転送する配列内のポインター要素の数を 0 番目の要素から指定できます。 式の値が 0 または負の場合、ランタイムエラーが発生します。

      配列スライス式 element-count-expr は、ポインター配列の各ポインターに適用される長さのセットを指定します。 1 つ目の長さは 1 つ目のポインターに、2 つ目の長さは 2 つ目のポインターに、というように適用されます。 転送する要素は常に 0 から始まります。

    • [preallocated] targetptr

      インテル® MIC アーキテクチャー上でのみメモリーを割り当てます。 CPU 上ではメモリーが割り当てられません。インテル® MIC アーキテクチャー上にメモリーを割り当てるには、preallocated を指定します。 この修飾子を使用すると、ユーザーによって割り当て済みのインテル® MIC アーキテクチャー上のメモリーがデータ転送で利用可能になります。 詳細は、「デバイス上でのみメモリーを割り当てる」を参照してください。

target ( target-name [ :target-number ])

target-name はターゲットを表します。インテル® Xeon Phi™ 製品の場合は mic を使用します。

target-numbersignal 節と wait 節に必須です。 target-number は整数式です。値は次のように解釈されます。

>=0

次の式に応じて、文を特定のターゲット上で実行します。

target = target-number % number_of_targets

例えば、4 つのターゲットを搭載したシステムの場合:

  • 2 または 6 を指定すると、ランタイムシステムはターゲット 2 でコードを実行します (2 % 4 と 6 % 4 はどちらも 2 になるため)。

  • 1000 を指定すると、ランタイムシステムはターゲット 0 でコードを実行します (1000 % 4 = 0 になるため)。

-1 または値なし

文をランタイムシステムによって選択されたターゲット上で実行します。

<= -1

予約済み。

target-numbersignal 節と wait 節に必須です。

ターゲットが利用できない場合、optional 節を一緒に指定しない限り、プログラムは実行に失敗してエラーメッセージを出力します。 optional 節は、ターゲットが利用できないときに文をホストで実行します。

オプション指示節

if-clause

ブール式。

式の評価結果

動作

true

文はターゲットで実行されます。

false

文はホストで実行されます。if-clausesignal 節あるいは wait 節を一緒に使用した場合の動作は不定です。

この節を使用してオフロードを有効にするかどうかを制御します。 関連するオフロード文がすべて有効または無効になるように、関連するプラグマでこの節を適切に使用してください。

mandatory

ターゲットで実行する必要がある場合に指定します。ホストで実行することはできません。

ターゲット・ハードウェアが利用できない場合にプログラムの実行を継続するには、statusvarname 変数を初期化して、このプラグマで status ( statusvarname ) 節を指定します。 後述の「説明」セクションでステータス変数の初期化方法と設定可能な値について説明します。

optional 節を指定しないと、この節を指定した場合と同じになります。 この節を明示的に指定することで、暗黙のデフォルトを明確にすることができます。

optional

ターゲットでの実行を要求しますが、必須ではありません。 ターゲットが利用できない場合にホストで実行するように指定します。

ターゲットではなく、ホストで文が実行された理由を確認するには、statusvarname 変数を初期化して、このプラグマで status ( statusvarname ) 節を指定します。 後述の「説明」セクションでステータス変数の初期化方法と設定可能な値について説明します。

mandatory 節はこの節の逆です。そのため、同じプラグマでこの節と一緒に使用しないでください。

signal ( tag )

非同期データ転送または計算処理のハンドルとなります。 演算はオフロードで処理され、ホストでプラグマ以降のコードを実行中に out 節によりオフロードから結果が返されます。 この節がない場合、オフロード全体と関連するデータ転送は同期して実行されます。 ホストは、プラグマが完了するまでそれ以降のコードを実行しません。

tag は、ポインターのサイズ値をベースライン言語で表現したもので、非同期データ転送または計算処理のハンドルとして使用されます。

この節を使用する場合、target 節で 0 以上の target-number を指定します。

status ( statusvarname )

オフロード構造の実行状態を特定します。 statusvarname 変数には、実行状態を示す値が含まれます。 後述の「説明」セクションでステータス変数の初期化方法と設定可能な値について説明します。

optional 節と一緒に使用し、ターゲットが利用できない場合、ターゲットで実行される文は代わりにホストで実行されます。

mandatory 節と一緒に使用し、ターゲットが利用できない場合、ターゲットで実行される文は無視され、プログラムは実行を継続します。 文が無視されたり、ホストで実行された理由を確認するには、この変数の値を調べます。

stream ( handle )

handle で指定した stream へオフロードします。 handle は、ストリームを作成するインテル® MIC アーキテクチャー・ベースのデバイスを指定する _Offload_create_stream 関数から取得されます。 オフロードは、ストリームが作成されたデバイスに対して実行されます。 詳細は、「ストリームを使用したオフロード」を参照します。

wait ( tag [, tag, ...] )

開始した非同期データ転送または非同期計算を待つ場合に指定します。

tag は、ポインターのサイズ値をベースライン言語で表現したものです。 signal 節と同じ表現の値が使用され、開始した非同期処理のハンドルとして使用されます。 ここで言う非同期処理は、非同期データ転送または非同期計算です。

この節を使用する場合、target 節で 0 以上の target-number を指定します。

シグナルが開始される前にシグナルを照会すると、未定義の動作を引き起こし、アプリケーションはランタイムアボートします。 例えば、target:1 で開始されたシグナルを target:0 で照会すると、シグナルは target:1 で開始されているため、target:0 に関連付けられたシグナルはなく、アプリケーションはランタイムアボートします。

説明

このプラグマは、非同期データ転送を開始し、同期データ転送を開始して完了します。

プラグマに続く文は、ターゲットが利用可能な場合、ターゲットで実行されます。 ターゲットが利用できない場合、optionalmandatory、および status (statusvarname ) 節により文の実行方法が決定されます。

指定する節

動作

optional

文はホストで実行されます。

optional および status (statusvarname )

文はホストで実行され、statusvarname にターゲットを利用できない理由が格納されます。

mandatory

文は無視され、プログラムは終了します。

mandatory および status (statusvarname )

文は無視され、プログラムは実行を継続します。statusvarname にターゲットを利用できない理由が格納されます。

statusvarname ステータス変数を初期化するには、OFFLOAD_STATUS_INIT(statusvarname) マクロを使用します。 ステータス変数の値は offload.h で定義されています。設定可能な値は次のとおりです。

説明

OFFLOAD_SUCCESS = 0

文はターゲットで実行されました。

OFFLOAD_DISABLED

文はターゲットで実行されませんでした。if-clause を指定し、その値が false の場合、文はホストで実行されました。

OFFLOAD_UNAVAILABLE

ターゲットが利用できないため、文はターゲットで実行されませんでした。

OFFLOAD_OUT_OF_MEMORY

offload-parameter 用のメモリーが不足していたため、文はターゲットで実行されませんでした。

OFFLOAD_PROCESS_DIED

ターゲットでランタイムエラーが発生し、ターゲットのプロセスが終了したため、文はターゲットで実行されませんでした。

OFFLOAD_ERROR

エラーが発生したため、文はターゲットで実行されませんでした。

2 つの異なるプラグマを使用してターゲットとホストからデータを非同期に受け取る例

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   // ホストは入力同期として f1 を送信
11   // 出力は f2 にあるが直ちに必要ではない
12   #pragma offload target (mic:0) signal(f2) \
13                          in(  f1 : length(N) ) \
14                          nocopy( f2 : length(N) ) signal(f2)
15   { 
16        foo(N, f1, f2);
17   }
..
20   #pragma offload_transfer (mic:0) wait(f2) \
                     out( f2 : length(N) alloc_if(0) free_if(1))
21   
22   // ホストは f2 の結果を使用可能

offload_target プラグマはデータ転送を開始し、offload_transfer プラグマはデータ転送が完了するのを待機します。

オフロードの入力をダブルバッファーにする例

#pragma offload_attribute(push, target(mic))
int count = 25000000;
int iter = 10;
float *in1, *out1;
float *in2, *out2;
#pragma offload_attribute(pop)

void do_async_in() {
      int i;
      #pragma offload_transfer target(mic:0) in(in1 : length(count) alloc_if(0) free_if(0) ) signal(in1)
      for (i=0; i<iter; i++) {
            if (i%2 == 0) {
                  #pragma offload_transfer target(mic:0) if(i!=iter-1) in(in2 : length(count) alloc_if(0) free_if(0) ) signal(in2)
                  #pragma offload target(mic:0) nocopy(in1) wait(in1) out(out1 : length(count) alloc_if(0) free_if(0) )
                  compute(in1, out1);
            } else {
                  #pragma offload_transfer target(mic:0) if(i!=iter-1) in(in1 : length(count) alloc_if(0) free_if(0) ) signal(in1)
                  #pragma offload target(mic:0) nocopy(in2) wait(in2) out(out2 : length(count) alloc_if(0) free_if(0) )
                  compute(in2, out2);
            }
      }
}

ターゲットでアライメントを指定するポインター配列の例

#define ARRAY_SIZE 4
#define DATA_ELEMS 100

__declspec(target(mic)) int start[ARRAY_SIZE];
__declspec(target(mic)) int len[ARRAY_SIZE];
__declspec(target(mic)) int align[ARRAY_SIZE];
__declspec(target(mic)) float *p[ARRAY_SIZE];
float *q[ARRAY_SIZE];

int main() {
  int i, j;
  bool failed = false;
  bool align_failed = false;

  for (i=0; i<ARRAY_SIZE; i++) {
    // ポインター配列要素の割り当て。利用可能なメモリーがあると仮定。
    p[i] = (float *)malloc(sizeof(float)*DATA_ELEMS);
    q[i] = (float *)malloc(sizeof(float)*DATA_ELEMS);
    p[i][0:DATA_ELEMS] = i;
    q[i][0:DATA_ELEMS] = p[i][0:DATA_ELEMS];
  }
 
  start[0] = 0;
  start[1] = 1;
  start[2] = 1;
  start[3] = 0;
  len[0] = DATA_ELEMS;
  len[1] = DATA_ELEMS - 2;
  len[2] = DATA_ELEMS - 2;
  len[3] = DATA_ELEMS;
  align[0] = 2048;
  align[1] = 4096;
  align[2] = 8192;
  align[3] = 8;

  // start と length はセクション
  // alloc_if、free_if のデフォルト値
  // アライメントを指定する
  // 配列の割り当ては element 0 から開始するが、転送はそうではない
  // いくつかの要素を更新して MIC から取得する
  // UUUUUUUUUUUUUU
  // .UUUUUUUUUUUU.
  // .UUUUUUUUUUUU.
  // UUUUUUUUUUUUUU

  #pragma offload target(mic) \
  inout( p[0:ARRAY_SIZE] : \
  extent(start[0:ARRAY_SIZE]:len[0:ARRAY_SIZE]) \
  align(align[0:ARRAY_SIZE]) ) { 
    for (i=0; i<ARRAY_SIZE; i++) {
       if (((long long)&p[i][0] & (align[i]-1)) != 0) {
          align_failed = true;
          printf("p[%d] failed alignment\n", i);
          fflush(0);
       }
    p[i][start[i]:len[i]] += 1.0;
    }
 }
 ...
 return 0;
}

ポインター配列と部分配列を使用する alloc_iffree_if の例

#define ARRAY_SIZE 4
#define DATA_ELEMS 100

__declspec(target(mic)) int start[ARRAY_SIZE];
__declspec(target(mic)) int len[ARRAY_SIZE];
__declspec(target(mic)) int allocif[ARRAY_SIZE];
__declspec(target(mic)) int freeif[ARRAY_SIZE];
__declspec(target(mic)) int *p[ARRAY_SIZE];

int main() {
  int i, j;
  bool failed = false;

  for (i=0; i<ARRAY_SIZE; i++) {
    // ポインター配列要素の割り当て。利用可能なメモリーがあると仮定。
    p[i] = (int *)malloc(sizeof(int)*DATA_ELEMS);
    p[i][0:DATA_ELEMS] = i;
  }
 
  start[:] = 1;
  len[:] = 98;

  // start と length はセクション
  // free_if と align のデフォルト値
  // alloc_if はベクトルを使用し、free_if はスカラー拡張を使用する
  // 割り当てのみ
  allocif[:] = 1;
  #pragma offload_transfer target(mic) \
  nocopy( p[0:ARRAY_SIZE] : \
  extent(start[0:ARRAY_SIZE]:len[0:ARRAY_SIZE]) \
  alloc_if(allocif[0:ARRAY_SIZE]) \
  free_if(0) )

  // メモリーを再利用してオフロードを行う
  #pragma offload target(mic) \
  inout( p[0:ARRAY_SIZE] : \
  extent(start[0:ARRAY_SIZE]:len[0:ARRAY_SIZE]) \
  alloc_if(0) \
  free_if(0) ) { 
    for (i=0; i<ARRAY_SIZE; i++) { p[i][start[i]:len[i]] += 1; }
  }

  // メモリーを解放する
  // alloc_if はスカラー、free_if はベクトル
  freeif[:] = 1;
  #pragma offload_transfer target(mic) \
  nocopy( p[0:ARRAY_SIZE] : \
  extent(start[0:ARRAY_SIZE]:len[0:ARRAY_SIZE]) \
  alloc_if(0) \
  free_if(freeif[0:ARRAY_SIZE]) )

 ...
 return 0;
}

intointo_extentalloc_extent を使用するポインター配列の例

#define ARRAY_SIZE 4
#define DATA_ELEMS 100
#define SEND_ROWS 2
#define SEND_COLS 50
#define P_OFFSET_IN 2
#define P_OFFSET_OUT 0
#define Q_OFFSET 0
#define COL_OFFSET 50

__declspec(target(mic)) int len[ARRAY_SIZE];
__declspec(target(mic)) short int *p[ARRAY_SIZE];
__declspec(target(mic)) short int *q[ARRAY_SIZE*2];

#ifdef RUN_ON_CPU
bool run_on_cpu = true;
#else
__declspec(target(mic)) bool run_on_cpu = false;
#endif

int main() {
  int i, j;
  bool failed = false;

  for (i=0; i<ARRAY_SIZE; i++) {
    // ポインター配列要素の割り当て。利用可能なメモリーがあると仮定。
    p[i] = (short int *)malloc(sizeof(short int)*DATA_ELEMS);
    q[i] = (short int *)malloc(sizeof(short int)*DATA_ELEMS);
    q[i+ARRAY_SIZE] = (short int *)malloc(sizeof(short int)*DATA_ELEMS);
    p[i][0:DATA_ELEMS] = i;
  }
 
  len[:] = SEND_COLS;

  // start と length の拡張にスカラーを使用
  // alloc_if、free_if、align のデフォルト値
  // MIC に転送されるデータと MIC から転送されるデータ
  // p[2][50:50] -> q[0][50:50] は指定された 50 要素のみ割り当てる
  // p[3][50:50] -> q[1][50:50] は指定された 50 要素のみ割り当てる
  // 計算を実行する
  // p[0][50:50] <- q[0][50:50]
  // p[1][50:50] <- q[1][50:50]

  #pragma offload target(mic) \
  in (p[P_OFFSET_IN:SEND_ROWS] : extent(COL_OFFSET:SEND_COLS) \
  into(q[Q_OFFSET:SEND_ROWS]) into_extent(COL_OFFSET:SEND_COLS) \
  alloc_extent(COL_OFFSET:len[0:SEND_ROWS]) ) \
  out(q[Q_OFFSET:SEND_ROWS] : extent(COL_OFFSET:SEND_COLS) \
  into(p[P_OFFSET_OUT:SEND_ROWS]) into_extent(COL_OFFSET:SEND_COLS) ) {
    for (i=0; i<SEND_ROWS; i++) {
      // CPU で実行する場合は "in into" を模倣する
      if (run_on_cpu) {
        q[Q_OFFSET+i][COL_OFFSET:SEND_COLS] = p[P_OFFSET_IN+i][COL_OFFSET:SEND_COLS];
      }

    q[Q_OFFSET+i][COL_OFFSET:SEND_COLS] += i*2;
   
    // CPU で実行する場合は "out into" を模倣する
    if (run_on_cpu) {
      p[P_OFFSET_OUT+i][COL_OFFSET:SEND_COLS] = q[Q_OFFSET+i][COL_OFFSET:SEND_COLS];
    }
  }
}
 ... 
 return 0;
}

関連情報