インテル® MIC アーキテクチャー向けの高度な最適化 - ピーク転送レートを達成する方法

同カテゴリーの次の記事

インテル® MIC アーキテクチャー向けの高度な最適化 - 精度を下げて最適化

この記事は、インテル® ソフトウェア・サイトに掲載されている「How to Achieve Peak Transfer Rate」の日本語参考訳です。


はじめに

ここでは、最適なデータ転送レートを測定する例を紹介します。この例では、malloc() と free() を _mm_malloc() と _mm_free() に置換し、インテル® Xeon Phi™ コプロセッサーへの DMA 転送に最適な 4K 境界でアライメントされたデータの割り当てと解放を行う方法を示します。実際のデータ転送レートは示していませんが、効率良いデータ転送手法を説明することを目的としています。

トピック

DMA 転送のパフォーマンスを最適化するには、4K 境界でアライメントされたデータ領域を割り当てる必要があります。DMA は、PCIe バスを利用してインテル® Xeon Phi™ コプロセッサーへ効率良くデータを転送するのに使用されます。

タイミングループの前に、インテル® メニー・インテグレーテッド・コア (インテル® MIC) アーキテクチャー側で _mm_malloc() を使用してデータを割り当てます。ループ内でデータ転送を行う場合は free_if(0) alloc_if(0) を使用します。以下に、簡単なコードを示します。

コードの実行方法:

**************

$ icc -offload-build bwtest.c

$ ./a.out -h

使用法:

./a.out -h -a <バッファーのアライメント> -d <デバイス ID> -n <反復回数>

$ ./a.out

Bandwidth test. Buffer alignment: 4096. DeviceID: 0. Number of iterations: 20.

          Size(Bytes) Send(Bytes/sec) Receive(Bytes/sec)
          <ここに結果が表示されます>

$

****************

ソースコード (bwtest.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <ia32intrin.h>

/* バッファーのアライメント */
static int align = 4096;

/* デバイス ID */
static int device = 0;

/* ベンチマーク・ループの反復回数 */
static int niters = 20;

/* CPU バッファー */
__declspec(target(mic))
static char* buf;

/* バッファーのサイズ  */
static const int bufsizes[] =
{
    4096,
    8192,
    16384,
    32768,
    65536,
    131072,
    262144,
    524288,
    1048576,
    2097152,
    4194304,
    8388608,
    16777216,
    33554432,
    67108864,
    134217728,
    268435456,
    536870912,
    0
};

static void parse_options(int argc, char** argv)
{
    int opt;

    while ((opt = getopt(argc, argv, "ha:d:n:")) != -1) {
        switch (opt) {
            case 'a':
                align = atoi(optarg);
                if (align <= 0 || align & (align-1) != 0) {
                    printf("Invalid alignment %d\n", align);
                    exit(1);
                }
                break;

            case 'd':
                device = atoi(optarg);
                if (device < 0) {
                    printf("Invalid device ID %d\n", device);
                    exit(1);
                }
                break;

            case 'n':
                niters = atoi(optarg);
                if (niters <= 0) {
                    printf("Invalid number of iterations %d\n", niters);
                    exit(1);
                }
                break;

            default:
                printf("Usage:\n\t%s -h -a <buffer alignment> -d <device ID> -n <number of iterations>\n", argv[0]);
                exit(0);
        }
    }
}

static inline double get_cpu_time()
{
    struct timeval tv;
    if (gettimeofday(&tv, 0)) {
        printf("gettimeofday returned error\n");
        abort();
    }
    return tv.tv_sec + tv.tv_usec/1e6;
}

int main(int argc, char **argv)
{
    int     i, j;
    double  send;
    double  receive;

    parse_options(argc, argv);

    printf("Bandwidth test. Buffer alignment: %d. DeviceID: %d. Number of iterations: %d.\n\n",
           align, device, niters);

    printf("%20s %20s %20s\n",
            "Size(Bytes)", "Send(Bytes/sec)", "Receive(Bytes/sec)");

    for (i = 0; bufsizes[i] > 0; i++) {
        /* CPU バッファーの割り当て */
        buf = (char*) _mm_malloc(bufsizes[i], align);
        if (buf == 0) {
            printf("Cannot not allocate buffer (%d bytes)\n", bufsizes[i]);
            abort();
        }

        /* MIC バッファーの割り当て */
#pragma offload target(mic: device) \
                in(buf : length(bufsizes[i]) free_if(0))
        {}

        /* メインのベンチマーク・ループ */
        send = 0;
        receive = 0;

        for (j = 0; j < niters; j++) {
            double start;

            /* 送信 */
            start = get_cpu_time();
#pragma offload target(mic: device) \
                in(buf : length(bufsizes[i]) alloc_if(0) free_if(0))
            {}
            send += get_cpu_time() - start;

            /* 受信 */
            start = get_cpu_time();
#pragma offload target(mic: device) \
                out(buf : length(bufsizes[i]) alloc_if(0) free_if(0))
            {}
            receive += get_cpu_time() - start;
        }

        send /= niters;
        receive /= niters;

        printf("%20d %20.2f %20.2f\n",
               bufsizes[i], bufsizes[i]/send, bufsizes[i]/receive);

        /* MIC バッファーの解放 */
#pragma offload target(mic: device) \
                out(buf : length(bufsizes[i]) alloc_if(0))
        {}

        /* CPU バッファーの解放 */
        _mm_free(buf);
    }

    return 0;
}

まとめ

ここでは、malloc() と free() の代わりに、_mm_malloc() と _mm_free() を使用して 4K 境界でアライメントされたデータバッファーを割り当てる方法を紹介しました。DMA 転送には 4K 境界が最適です。このコードでは、さまざまなバッファーサイズの転送レートを測定するコードも提供しています。このコードは、データの最適なバッファーサイズを特定するのに役立ちます。

次のステップ

この記事は、「Programming and Compiling for Intel® Many Integrated Core Architecture」(英語) の一部「How to Achieve Peak Transfer Rate」の翻訳です。インテル® Xeon Phi™ コプロセッサー上にアプリケーションを移植し、チューニングを行うには、本ガイドの各リンクのトピックを参照してください。アプリケーションのパフォーマンスを最大限に引き出すために必要なステップを紹介しています。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事