exampleを読んで、arrayfireのコーディングが気になった

六花です。

Reccurent Batch NormalizationにBatch ReNormalizationを組み込んで遊んでたら結構な日が経過してました。
そのとき、Arrayfireの処理速度が悲しいくらい遅かったので、速度改善のヒントをネットで探してみたもののうまく引っかからず……そういえばexample読んだことなかったので読んでみたんです。
読んでみると、af::arrayを自動変数として都度宣言しているのが気になったので、どういう書き方が速いか試してみることにしました。

■環境

Microsoft Visual Studio Community 2019 Version 16.4.4
Arrayfire 3.6.4

■試験概要

・af::matmulをfor文で何度も実行して時間を計測する
・for文の前でaf::arrayを宣言するもの、for文の中でaf::arrayを宣言するもの、そしてmatmulの後evalで評価確定するものを組み合わせる
・af::arrayを構造体に組み込んで同じように時間を計測する
・おまけ:matmulに対するmatmulNTの有用性(matmulNTは二つ目のaf::arrayを転置してからmatmulを実行する)

■先に結論

・for文で実行する回数が少ない場合、最速と最遅で10%程度の違い
・しかし、for文で実行する回数を多くした場合、最速と最遅で2%程度の違い
・JITで最適化されてる? evalの使い方は以下のサイトを読む限り間違ってそうhttps://arrayfire.com/performance-improvements-to-jit-in-arrayfire-v3-4/


#include <arrayfire.h>
#include <iostream>

int main()
{
    af::info();

    constexpr int size = 10000;
    constexpr int epoch = 100;

    // dummy
    // 最初の処理は動作時間が多くなるので、時間計測するなら準備運動させてあげる必要がある
    {
        af::array A = af::randu(size, size);
        af::array B = af::randu(size, size);
        af::array C = af::constant(0.0, size, size);

        for (int i = 0; i < epoch; ++i)
        {
            C = af::matmul(A, B);
        }
        af::sync();
        auto stop_clock = af::timer::stop();
    }

    //---------------------
    // 前もって宣言した方がほんの少し速いが、クラスでまとめるのは都度af::arrayを宣言するより遅い
    // evalはほんの少し遅くなるので同期で問題になるときだけ

    std::cout << "前もって宣言" << std::endl;
    {
        af::timer::start();

        af::array A = af::randu(size, size);
        af::array B = af::randu(size, size);
        af::array C = af::constant(0.0, size, size);

        for (int i = 0; i < epoch; ++i)
        {
            C = af::matmul(A, B);
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    {
        af::timer::start();

        af::array A = af::randu(size, size);
        af::array B = af::randu(size, size);
        af::array C = af::constant(0.0, size, size);

        for (int i = 0; i < epoch; ++i)
        {
            C = af::matmul(A, B);
            C.eval();
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    std::cout << "for文の中で宣言" << std::endl;
    {
        af::timer::start();

        for (int i = 0; i < epoch; ++i)
        {
            af::array A = af::randu(size, size);
            af::array B = af::randu(size, size);
            af::array C = matmul(A, B);
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    {
        af::timer::start();

        for (int i = 0; i < epoch; ++i)
        {
            af::array A = af::randu(size, size);
            af::array B = af::randu(size, size);
            af::array C = af::matmul(A, B);
            C.eval();
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    std::cout << "クラスを定義" << std::endl;
    {
        struct AB
        {
            af::array A;
            af::array B;
            AB()
            {
                A = af::randu(size, size);
                B = af::randu(size, size);
            }
        };
        AB ab;

        af::timer::start();
        for (int i = 0; i < epoch; ++i)
        {
            af::array C = matmul(ab.A, ab.B);
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    {
        struct AB
        {
            af::array A;
            af::array B;
            AB()
            {
                A = af::randu(size, size);
                B = af::randu(size, size);
            }
        };

        af::timer::start();
        for (int i = 0; i < epoch; ++i)
        {
            AB ab;
            af::array C = matmul(ab.A, ab.B);
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    //----------------------
    // matmul(A, B.T())より matmulNT(A, B)の方が1割くらい早そう

    std::cout << "matmul、matmulNTの調査" << std::endl;
    {
        af::timer::start();

        af::array A = af::randu(size, size);
        af::array B = af::randu(size, size);
        af::array C = af::constant(0.0, size, size);

        for (int i = 0; i < epoch; ++i)
        {
            C = af::matmul(A, B.T());
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    {
        af::timer::start();

        af::array A = af::randu(size, size);
        af::array B = af::randu(size, size);
        af::array C = af::constant(0.0, size, size);

        for (int i = 0; i < epoch; ++i)
        {
            C = af::matmulNT(A, B);
        }
        af::sync();
        auto stop_clock = af::timer::stop();
        std::cout << stop_clock << std::endl;
    }

    std::cout << "end" << std::endl;
    while(true){}
}

■出力

size = 10000、 epoch = 100の出力


ArrayFire v3.6.4 (CUDA, 64-bit Windows, build 1b8030c)
Platform: CUDA Toolkit 10.0, Driver: 10020
[0] GeForce GTX 1080 Ti, 11264 MB, CUDA Compute 6.1
-1- GeForce GTX 1080, 8192 MB, CUDA Compute 6.1
前もって宣言
20.9175
21.0592
for文の中で宣言
21.4592
21.8435
クラスを定義
22.7908
23.7657
matmul、matmulNTの調査
24.06
22.0858
end

size = 10000、 epoch = 10000の出力


ArrayFire v3.6.4 (CUDA, 64-bit Windows, build 1b8030c)
Platform: CUDA Toolkit 10.0, Driver: 10020
[0] GeForce GTX 1080 Ti, 11264 MB, CUDA Compute 6.1
-1- GeForce GTX 1080, 8192 MB, CUDA Compute 6.1
前もって宣言
2312.39
2318.38
for文の中で宣言
2352.78
2361.55
クラスを定義
2339.08
2350.49
matmul、matmulNTの調査
2376.83
2215.6
end

おすすめ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です