質問

の使用方法について詳しく知りたい SSE.

当然のことですが、本を読む以外にどのような方法で学ぶことができますか? インテル® 64 および IA-32 アーキテクチャー ソフトウェア開発者マニュアル?

主に私は、 GCC X86 組み込み関数.

役に立ちましたか?

解決

まず、私は、組み込み関数を使用してお勧めしません - 彼らは(同じアーチのコンパイラ間で)移植できません。

使用組み込み関数に、GCC <のhref = "のhttp://www.liranuna。 COM / SSE-組み込み関数・最適化・イン・人気・コンパイラ/」のrel = "nofollowをnoreferrer">さらに最適化されたコードの中にSSEの組み込み関数を最適化する素晴らしい仕事をしていません。あなたはいつもアセンブリでのぞき見を持っており、それの能力を最大限にSSEを使用する方法を見ることができます。

組み込み関数は簡単です - ちょうど、通常の関数呼び出しのように:

#include <immintrin.h>  // portable to all x86 compilers

int main()
{
    __m128 vector1 = _mm_set_ps(4.0, 3.0, 2.0, 1.0); // high element first, opposite of C array order.  Use _mm_setr_ps if you want "little endian" element order in the source.
    __m128 vector2 = _mm_set_ps(7.0, 8.0, 9.0, 0.0);

    __m128 sum = _mm_add_ps(vector1, vector2); // result = vector1 + vector 2

    vector1 = _mm_shuffle_ps(vector1, vector1, _MM_SHUFFLE(0,1,2,3));
    // vector1 is now (1, 2, 3, 4) (above shuffle reversed it)
    return 0;
}
アレイからの荷重データに

使用_mm_load_ps又は_mm_loadu_ps

もちろんの方法より多くのオプションがあり、SSEは、比較的容易に学ぶことは本当に強力で、私の意見である。

ガイドへのリンクのためにも、 https://stackoverflow.com/tags/sse/info のを参照してください。

他のヒント

あなたがリソースを求めているのでます:

A C ++でのSSEを使用するための実用的なガイド:SSEを効果的に使用する方法の良い概念の概要を、例と

をrel="noreferrer">:総合すべてのあなたの本質的なニーズのための参照。それはMSDNのですが、ここに記載されているほとんどすべての組み込み関数は、同様GCCおよびICCによってサポートされています。

クリストファー・ライトのSSEページはを:SSEの意味でのクイックリファレンスオペコード。私は、Intelのマニュアルは、同じ機能を果たすことができますが、これは高速であると思います。

これは、組み込み関数でコードのほとんどを書くことは、それが効率的なコードを生成だことを確認するために、あなたのコンパイラの出力のobjdumpのをチェックしますかおそらく最高です。 SIMDコード生成には、まだかなり新しい技術であり、それは、コンパイラはいくつかのケースでは、それは間違っているかもしれませんということは非常に可能です。

私は博士Agner霧の研究&最適化ガイドは非常に貴重見つけます!彼はまた、いくつかのライブラリ&Iは、まだ試していないテストツールを持っています。 http://www.agner.org/optimize/する

ステップ1:アセンブリを手動で作成する

学習を開始するときに、何が起こっているかを正確に確認して制御するために、まず手動で独自のアセンブリを作成してみることをお勧めします。

次に、プログラム内で何が起こっているかをどのように観察するかが問題になります。その答えは次のとおりです。

  • GDB
  • C標準ライブラリを使用して、 print そして assert もの

C 標準ライブラリを自分で使用するには、少しの作業が必要ですが、それほど大きな作業は必要ありません。たとえば、私は Linux 上のテスト セットアップの次のファイルでこの作業をうまく実行しました。

これらのヘルパーを使用して、次のような基本的な部分をいじり始めます。

  • メモリとの間でデータを SSE レジスタにロードおよびストアする
  • さまざまなサイズの整数と浮動小数点数を加算します
  • 結果は私が期待したものであると主張する

addpd.S

#include <lkmc.h>

LKMC_PROLOGUE
.data
    .align 16
    addps_input0: .float 1.5, 2.5,  3.5,  4.5
    addps_input1: .float 5.5, 6.5,  7.5,  8.5
    addps_expect: .float 7.0, 9.0, 11.0, 13.0
    addpd_input0: .double 1.5, 2.5
    addpd_input1: .double 5.5, 6.5
    addpd_expect: .double 7.0, 9.0
.bss
    .align 16
    output:       .skip 16
.text
    /* 4x 32-bit */
    movaps addps_input0, %xmm0
    movaps addps_input1, %xmm1
    addps %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, addps_expect, $0x10)

    /* 2x 64-bit */
    movaps addpd_input0, %xmm0
    movaps addpd_input1, %xmm1
    addpd %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, addpd_expect, $0x10)
LKMC_EPILOGUE

GitHub アップストリーム.

パドクS

#include <lkmc.h>

LKMC_PROLOGUE
.data
    .align 16
    input0:       .long 0xF1F1F1F1, 0xF2F2F2F2, 0xF3F3F3F3, 0xF4F4F4F4
    input1:       .long 0x12121212, 0x13131313, 0x14141414, 0x15151515
    paddb_expect: .long 0x03030303, 0x05050505, 0x07070707, 0x09090909
    paddw_expect: .long 0x04030403, 0x06050605, 0x08070807, 0x0A090A09
    paddd_expect: .long 0x04040403, 0x06060605, 0x08080807, 0x0A0A0A09
    paddq_expect: .long 0x04040403, 0x06060606, 0x08080807, 0x0A0A0A0A
.bss
    .align 16
    output:       .skip 16
.text
    movaps input1, %xmm1

    /* 16x 8bit */
    movaps input0, %xmm0
    paddb %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddb_expect, $0x10)

    /* 8x 16-bit */
    movaps input0, %xmm0
    paddw %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddw_expect, $0x10)

    /* 4x 32-bit */
    movaps input0, %xmm0
    paddd %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddd_expect, $0x10)

    /* 2x 64-bit */
    movaps input0, %xmm0
    paddq %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddq_expect, $0x10)

LKMC_EPILOGUE

GitHub アップストリーム.

ステップ2:いくつかの組み込み関数を書く

ただし、実稼働コードの場合は、次で説明されているように、生のアセンブリではなく既存の組み込みを使用することをお勧めします。 https://stackoverflow.com/a/1390802/895245

そこでここで、前の例を組み込み関数を使用してほぼ同等の C コードに変換してみます。

addpq.c

#include <assert.h>
#include <string.h>

#include <x86intrin.h>

float global_input0[] __attribute__((aligned(16))) = {1.5f, 2.5f, 3.5f, 4.5f};
float global_input1[] __attribute__((aligned(16))) = {5.5f, 6.5f, 7.5f, 8.5f};
float global_output[4] __attribute__((aligned(16)));
float global_expected[] __attribute__((aligned(16))) = {7.0f, 9.0f, 11.0f, 13.0f};

int main(void) {
    /* 32-bit add (addps). */
    {
        __m128 input0 = _mm_set_ps(1.5f, 2.5f, 3.5f, 4.5f);
        __m128 input1 = _mm_set_ps(5.5f, 6.5f, 7.5f, 8.5f);
        __m128 output = _mm_add_ps(input0, input1);
        /* _mm_extract_ps returns int instead of float:
        * * https://stackoverflow.com/questions/5526658/intel-sse-why-does-mm-extract-ps-return-int-instead-of-float
        * * https://stackoverflow.com/questions/3130169/how-to-convert-a-hex-float-to-a-float-in-c-c-using-mm-extract-ps-sse-gcc-inst
        * so we must use instead: _MM_EXTRACT_FLOAT
        */
        float f;
        _MM_EXTRACT_FLOAT(f, output, 3);
        assert(f == 7.0f);
        _MM_EXTRACT_FLOAT(f, output, 2);
        assert(f == 9.0f);
        _MM_EXTRACT_FLOAT(f, output, 1);
        assert(f == 11.0f);
        _MM_EXTRACT_FLOAT(f, output, 0);
        assert(f == 13.0f);

        /* And we also have _mm_cvtss_f32 + _mm_shuffle_ps, */
        assert(_mm_cvtss_f32(output) == 13.0f);
        assert(_mm_cvtss_f32(_mm_shuffle_ps(output, output, 1)) == 11.0f);
        assert(_mm_cvtss_f32(_mm_shuffle_ps(output, output, 2)) ==  9.0f);
        assert(_mm_cvtss_f32(_mm_shuffle_ps(output, output, 3)) ==  7.0f);
    }

    /* Now from memory. */
    {
        __m128 *input0 = (__m128 *)global_input0;
        __m128 *input1 = (__m128 *)global_input1;
        _mm_store_ps(global_output, _mm_add_ps(*input0, *input1));
        assert(!memcmp(global_output, global_expected, sizeof(global_output)));
    }

    /* 64-bit add (addpd). */
    {
        __m128d input0 = _mm_set_pd(1.5, 2.5);
        __m128d input1 = _mm_set_pd(5.5, 6.5);
        __m128d output = _mm_add_pd(input0, input1);
        /* OK, and this is how we get the doubles out:
        * with _mm_cvtsd_f64 + _mm_unpackhi_pd
        * https://stackoverflow.com/questions/19359372/mm-cvtsd-f64-analogon-for-higher-order-floating-point
        */
        assert(_mm_cvtsd_f64(output) == 9.0);
        assert(_mm_cvtsd_f64(_mm_unpackhi_pd(output, output)) == 7.0);
    }

    return 0;
}

GitHub アップストリーム.

パドクc

#include <assert.h>
#include <inttypes.h>
#include <string.h>

#include <x86intrin.h>

uint32_t global_input0[] __attribute__((aligned(16))) = {1, 2, 3, 4};
uint32_t global_input1[] __attribute__((aligned(16))) = {5, 6, 7, 8};
uint32_t global_output[4] __attribute__((aligned(16)));
uint32_t global_expected[] __attribute__((aligned(16))) = {6, 8, 10, 12};

int main(void) {

    /* 32-bit add hello world. */
    {
        __m128i input0 = _mm_set_epi32(1, 2, 3, 4);
        __m128i input1 = _mm_set_epi32(5, 6, 7, 8);
        __m128i output = _mm_add_epi32(input0, input1);
        /* _mm_extract_epi32 mentioned at:
        * https://stackoverflow.com/questions/12495467/how-to-store-the-contents-of-a-m128d-simd-vector-as-doubles-without-accessing/56404421#56404421 */
        assert(_mm_extract_epi32(output, 3) == 6);
        assert(_mm_extract_epi32(output, 2) == 8);
        assert(_mm_extract_epi32(output, 1) == 10);
        assert(_mm_extract_epi32(output, 0) == 12);
    }

    /* Now from memory. */
    {
        __m128i *input0 = (__m128i *)global_input0;
        __m128i *input1 = (__m128i *)global_input1;
        _mm_store_si128((__m128i *)global_output, _mm_add_epi32(*input0, *input1));
        assert(!memcmp(global_output, global_expected, sizeof(global_output)));
    }

    /* Now a bunch of other sizes. */
    {
        __m128i input0 = _mm_set_epi32(0xF1F1F1F1, 0xF2F2F2F2, 0xF3F3F3F3, 0xF4F4F4F4);
        __m128i input1 = _mm_set_epi32(0x12121212, 0x13131313, 0x14141414, 0x15151515);
        __m128i output;

        /* 8-bit integers (paddb) */
        output = _mm_add_epi8(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x03030303);
        assert(_mm_extract_epi32(output, 2) == 0x05050505);
        assert(_mm_extract_epi32(output, 1) == 0x07070707);
        assert(_mm_extract_epi32(output, 0) == 0x09090909);

        /* 32-bit integers (paddw) */
        output = _mm_add_epi16(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x04030403);
        assert(_mm_extract_epi32(output, 2) == 0x06050605);
        assert(_mm_extract_epi32(output, 1) == 0x08070807);
        assert(_mm_extract_epi32(output, 0) == 0x0A090A09);

        /* 32-bit integers (paddd) */
        output = _mm_add_epi32(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x04040403);
        assert(_mm_extract_epi32(output, 2) == 0x06060605);
        assert(_mm_extract_epi32(output, 1) == 0x08080807);
        assert(_mm_extract_epi32(output, 0) == 0x0A0A0A09);

        /* 64-bit integers (paddq) */
        output = _mm_add_epi64(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x04040404);
        assert(_mm_extract_epi32(output, 2) == 0x06060605);
        assert(_mm_extract_epi32(output, 1) == 0x08080808);
        assert(_mm_extract_epi32(output, 0) == 0x0A0A0A09);
    }

    return 0;

GitHub アップストリーム.

ステップ 3:いくつかのコードを最適化し、ベンチマークを実行します

最後の、最も重要で難しいステップは、もちろん、実際に組み込み関数を使用してコードを高速化し、改善のベンチマークを行うことです。

そのためには、x86 マイクロアーキテクチャについて少し学ぶ必要があると思われますが、私自身はよく知りません。CPU と IO の制限は、おそらく次のような問題の 1 つになるでしょう。 「CPU バウンド」および「I/O バウンド」という用語は何を意味しますか?

で述べたように: https://stackoverflow.com/a/12172046/895245 これには、ほぼ必然的に Agner Fog のドキュメントを読む必要がありますが、これは Intel 自体が公開しているものよりも優れているようです。

ただし、ステップ 1 と 2 が、少なくとも機能以外のパフォーマンス面を実験し、命令が何を行っているかをすぐに確認するための基礎として機能することを願っています。

TODO:ここでは、そのような最適化の最小限の興味深い例を作成します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top