cudaMemcpyの時間を計測してみる

cudaMemcpyの実行時間を計測してみました。環境条件は次の通りです。

1KB転送

とりあえず1KBの転送をしてみます。

コード

#include <algorithm>
#include <cstdio>
#include <ctime>
#include <stdint.h>

#define N 1024

static inline void
print_msec(const char * s, clock_t c)
{
    double msec = (static_cast<double>(c) / CLOCKS_PER_SEC) * 1000;
    printf("%s : %f\n", s, msec);
}

int main(void)
{
    uint8_t * d_tmp = NULL;
    uint8_t input[N];
    uint8_t output[N];

    for (int i = 0; i < N; ++i) input[i] = i & 0xFF;
    std::fill(output, output + N, 0x00);

    clock_t c0 = clock();
    cudaMalloc(&d_tmp, N);
    clock_t c1 = clock();
    for(int i = 0; i < N * N; ++i) cudaMemcpy(d_tmp, input, N, cudaMemcpyHostToDevice);
    clock_t c2 = clock();
    for(int i = 0; i < N * N; ++i) cudaMemcpy(output, d_tmp, N, cudaMemcpyDeviceToHost);
    clock_t c3 = clock();
    //for (int i = 0; i < N; ++i) std::cout << static_cast<int>(output[i]) << std::endl;
    cudaFree(d_tmp);
    clock_t c4 = clock();

    print_msec("cudaMalloc", c1 - c0);
    print_msec("cudaMemcpyHostToDevice", c2 - c1);
    print_msec("cudaMemcpyDeviceToHost", c3 - c2);
    print_msec("cudaFree", c4 - c3);

    return 0;
}

実行結果

cudaMalloc : 50.000000
cudaMemcpyHostToDevice : 11070.000000
cudaMemcpyDeviceToHost : 10780.000000
cudaFree : 0.000000

1KBのデータを1024*1024回送受信してます。これを見る限り上りと下りの速度差はないようです。1KB転送の平均時間は大体10usでしょうか?心配になったのでtimeコマンドで測って見ましたが大体22sだったので計測結果の表示がは間違っている分けではなさそうです。
大体100MB/sくらいでしょうか?メインメモリではなくデバイスドライバPCI Expressを経由して流れていく事を考えると、簡単に試した段階ではこんなもんですかね。

1MB転送

1KBという細切れで送っているせいでレイテンシが大きくでているのかとも思い、試しにデータサイズを1MBして送受信してみました。

コード

#include <algorithm>
#include <cstdio>
#include <ctime>
#include <stdint.h>

#define N (1024 * 1024)

static inline void
print_msec(const char * s, clock_t c)
{
    double msec = (static_cast<double>(c) / CLOCKS_PER_SEC) * 1000;
    printf("%s : %f\n", s, msec);
}

int main(void)
{
    uint8_t * d_tmp = NULL;
    uint8_t input[N];
    uint8_t output[N];

    for (int i = 0; i < N; ++i) input[i] = i & 0xFF;
    std::fill(output, output + N, 0x00);

    clock_t c0 = clock();
    cudaMalloc(&d_tmp, N);
    clock_t c1 = clock();
    for(int i = 0; i < 1024; ++i) cudaMemcpy(d_tmp, input, N, cudaMemcpyHostToDevice);
    clock_t c2 = clock();
    for(int i = 0; i < 1024; ++i) cudaMemcpy(output, d_tmp, N, cudaMemcpyDeviceToHost);
    clock_t c3 = clock();
    //for (int i = 0; i < N; ++i) std::cout << static_cast<int>(output[i]) << std::endl;
    cudaFree(d_tmp);
    clock_t c4 = clock();

    print_msec("cudaMalloc", c1 - c0);
    print_msec("cudaMemcpyHostToDevice", c2 - c1);
    print_msec("cudaMemcpyDeviceToHost", c3 - c2);
    print_msec("cudaFree", c4 - c3);

    return 0;
}

実行結果

cudaMalloc : 50.000000
cudaMemcpyHostToDevice : 770.000000
cudaMemcpyDeviceToHost : 760.000000
cudaFree : 0.000000

今度は1MBを1024回送受信しました。だいたい1.3GB/sでしょうか。やはり細切れのレイテンシが遅くしていたようです。メインメモリもDDR2で実測すると一桁前半GB/sくらいなので、外部機器に流すということからすると1.3GB/sかなりいい成績な気がします。

512MB転送

さらに大きくして512MBを一回で送受信してみます。

コード

#include <algorithm>
#include <cstdio>
#include <ctime>
#include <stdint.h>

#define N (512 * 1024 * 1024)

uint8_t input[N];
uint8_t output[N];

static inline void
print_msec(const char * s, clock_t c)
{
    double msec = (static_cast<double>(c) / CLOCKS_PER_SEC) * 1000;
    printf("%s : %f\n", s, msec);
}

int main(void)
{
    uint8_t * d_tmp = NULL;

    for (int i = 0; i < N; ++i) input[i] = i & 0xFF;
    std::fill(output, output + N, 0x00);
    clock_t c0 = clock();
    cudaMalloc(&d_tmp, N);
    clock_t c1 = clock();
    cudaMemcpy(d_tmp, input, N, cudaMemcpyHostToDevice);
    clock_t c2 = clock();
    cudaMemcpy(output, d_tmp, N, cudaMemcpyDeviceToHost);
    clock_t c3 = clock();
    //for (int i = 0; i < N; ++i) std::cout << static_cast<int>(output[i]) << std::endl;
    cudaFree(d_tmp);
    clock_t c4 = clock();

    print_msec("cudaMalloc", c1 - c0);
    print_msec("cudaMemcpyHostToDevice", c2 - c1);
    print_msec("cudaMemcpyDeviceToHost", c3 - c2);
    print_msec("cudaFree", c4 - c3);

    return 0;
}

実行結果

cudaMalloc : 40.000000
cudaMemcpyHostToDevice : 230.000000
cudaMemcpyDeviceToHost : 230.000000
cudaFree : 0.000000

何気なく512MBの配列2個をauto変数で取ろうしたらsegmentation faultしちゃいました。気をつけましょう。それはいいとして、512MBで230msなのでだいたい2.2GB/sでしょうか。メインメモリに対して遅いmemcpy()実行するよりも、場合によっては速いかもしれません。

まとめ

Cell/B.E.は25.6GB/sなのでそれに比べるとまだ1桁遅いわけですが、一万円くらいで買える(本来のGPUの用途の為に既に持ってる人もいる)PCのアクセラレータとして、2.2GB/sという転送速度はいい速度なんじゃないでしょうか。PCI Expressを経由するのでデータのやりとりが完全にネックになって、肝心の演算器を全然使いこなせない可能性を危惧していたのですが、これだけ速度が出るならなにか使える場面はありそうです。