性能評価:予測と分岐・設定とクリア

予測と分岐・設定とクリアのプログラムを応用して、C++言語で記述したプログラムと、アセンブリ言語で記述した関数を利用したときの性能差を検証してみましょう。性能評価は「予測と分岐・設定とクリア・32ビット浮動小数点型」を用います。
arques.hatenablog.com

性能評価プログラム

C++で記述した関数と、アセンブリ言語で記述した関数を複数回呼び出します。それぞれに要した時間をclock関数で計測し、性能を評価します。common.hやアセンブリ言語で記述した関数は「予測と分岐・設定とクリア・32ビット浮動小数点型」で示したものと同じです。

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

// asmbler関数名: 処理+条件+型
#define T float
extern "C"
{
    void acmpzltps(T*, const T, const size_t, const T);
    void acmpzeqps(T*, const T, const size_t, const T);
    void acmpzleps(T*, const T, const size_t, const T);
    void acmpzneps(T*, const T, const size_t, const T);
    void acmpzgeps(T*, const T, const size_t, const T);
    void acmpzgtps(T*, const T, const size_t, const T);
}
typedef void (*Dfunc)(T*, const T, const size_t, const T);

extern "C"
Dfunc afunc[] = { acmpzeqps, acmpzltps, acmpzleps, acmpzneps, acmpzgeps, acmpzgtps };
Dfunc cfunc[] = { ccmpzeq,   ccmpzlt,   ccmpzle,   ccmpzne,   ccmpzge,   ccmpzgt  };

// main, cmpValueと条件に従って比較し、
//      trueならvalueへ、そうでないなら 0 へ
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], c[ArrLen];
    const T cmpValue = 8, value = 12;

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

        clock_t start;
        for (int i = 0; i < sizeof(cfunc) / sizeof(*cfunc);i++)
        {
            #define LOOPS   10000
            cout << "---[" << i << "]---  " << endl;

            init(a, c, ArrLen);

            start = clock();
            for (int j = 0; j < LOOPS;j++)
                cfunc[i](c, cmpValue, ArrLen, value);
            print_elTime("cpp: ", start, clock());

            start = clock();
            for (int j = 0; j < LOOPS;j++)
                afunc[i](a, cmpValue, ArrLen, value);
            print_elTime("asm: ", start, clock());

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


性能評価

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

グラフだけでなく、表も示します。

アセンブリ言語で記述した関数が高速で、期待した結果が得られます。ただ、条件に「==」を指定したときに、アセンブリ言語で開発した関数とC++言語で開発した関数に有意な差を認められません。これの原因を調査するにはプロファイラやアセンブリコードを観察する必要があります。細かなアセンブリコードの観察などは後述する予定です。ここでは、単に、性能を観察するに留めます。

次に /O1 オプションを指定してビルド&実行したときの性能を示します。

表も示します。

C++コンパイラのオプションを変更していますので、当然ながらアセンブリ言語で開発した関数の実行速度は影響を受けません。C++言語で開発した関数は、/O1 と /O2 で顕著な速度差を観察できます。興味深いのは、条件にイコール、あるいはノットイコールを指定したとき、C++言語で開発した関数が高速に動作する点です。