性能評価:最小値・最大値・インデックス

一次元配列から最小値・最大値を探すプログラムを応用して、C++言語で記述したプログラムと、アセンブリ言語で記述した関数を利用したときの性能差を検証してみましょう。
arques.hatenablog.com

64ビット浮動小数点型

性能評価プログラム

C++で記述した関数と、アセンブリ言語で記述した関数を複数回呼び出します。それぞれに要した時間をclock関数で計測し、性能を評価します。common.hやアセンブリ言語で記述した関数は「最小値・最大値」で示したものと同じです。以降に、呼び出し側のソースリストを示します。

#include "..\common.h"  // TEMPLATES

#define T double
extern "C"
{
    T aminpd(const T*, const size_t, int*);
    T amaxpd(const T*, const size_t, int*);
}

// main
int main(void)
{
    const size_t ArrLen = 32768;
    static_assert(ArrLen % 16 == 0,
        "number of elements must be an integral multiple of 16.");
    //T a[ArrLen];
    int   cminIndex, aminIndex, cmaxIndex, amaxIndex;

    try
    {
        T cmin, amin, cmax, amax;

        T* a = (T*)_mm_malloc(sizeof(T) * ArrLen, 64);

        clock_t start;

        #define LOOPS   10000
        init(a, ArrLen);

        start = clock();
        for (int j = 0; j < LOOPS;j++)
            cmin = cMin  (a, ArrLen, &cminIndex);
        print_elTime("cpp: ", start, clock());

        start = clock();
        for (int j = 0; j < LOOPS;j++)
            amin = aminpd(a, ArrLen, &aminIndex);
        print_elTime("asm: ", start, clock());


        start = clock();
        for (int j = 0; j < LOOPS;j++)
            cmax = cMax  (a, ArrLen, &cmaxIndex);
        print_elTime("cpp: ", start, clock());

        start = clock();
        for (int j = 0; j < LOOPS;j++)
            amax = amaxpd(a, ArrLen, &amaxIndex);
        print_elTime("asm: ", start, clock());


        cout << fixed << setprecision(2);
        cout << "cpp min = " << cmin << ", index = " << cminIndex << endl;
        cout << "asm min = " << amin << ", index = " << aminIndex << endl;
        cout << "cpp max = " << cmax << ", index = " << cmaxIndex << endl;
        cout << "asm max = " << amax << ", index = " << amaxIndex << endl;

        _mm_free(a);
    }
    catch (char* str)
    {
        cerr << str << endl;
    }
    return 0;
}

これまでと同様の方法で性能評価します。

上記プログラムをビルド&実行したときの性能を示します。プログラムを起動すると、それぞれに要した時間が表示されます。横軸は比較の種類、縦軸は処理に要した時間(ミリ秒)です。

アセンブリ言語で開発した関は、C++言語で開発した関数より約6倍強高速です。レジスターサイズを考えると理想が8倍ですがオーバヘッドの影響があるようです。

32ビット浮動小数点型

32ビット浮動小数点型関数の性能を観察してみます。アセンブリ言語で開発した関数は、対応する型用に開発したものを使用します。以降に、呼び出し側のソースリストを示しが、直前のプログラムに近いため一部だけ示します。

#include "..\common.h"  // TEMPLATES

#define T float
extern "C"
{
    T aminps(const T*, const size_t, int*);
    T amaxps(const T*, const size_t, int*);
}

// main
int main(void)
{
        :
        for (int j = 0; j < LOOPS;j++)
            amin = aminps(a, ArrLen, &aminIndex);
        print_elTime("asm: ", start, clock());

        :
        for (int j = 0; j < LOOPS;j++)
            amax = amaxps(a, ArrLen, &amaxIndex);
        print_elTime("asm: ", start, clock());
        :

アセンブリ言語で開発した関数が変わるだけで、ほかの部分は先のプログラムと同様です。

上記プログラムをビルド&実行したときの性能を示します。


アセンブリ言語で開発した関は、C++言語で開発した関数より約12倍から13倍高速です。レジスターで処理できる要素が、先のプログラムの倍になるため、予想通りの高速化が図られています。