14

一般的に、SSE / MMXに関連して「ネット上で」出くわすものはすべて、ベクトルとマトラシーの数学的なものとして出てきます。ただし、 Agner Fogが提供するような、SSEに最適化された「標準関数」のライブラリ、またはGCCのSSEベースの文字列スキャンアルゴリズムの一部を探しています。

簡単な一般的な要約として:これらは、memset、memcpy、strstr、memcmp BSR / BSF、つまりSSEintrsuctionsから構築されたstdlib風のものになります。

アセンブリではなく組み込み関数を使用するSSE1(正式にはMMX2)用にするのが望ましいですが、どちらでも構いません。うまくいけば、これはあまり広い範囲ではありません。

アップデート1

いくつか検索した後、私はいくつかの有望なものに出くわしました、1つのライブラリが私の目を引きました:

  • LibFreeVec:mac / IBMのみのようです(AltiVecベースであるため)、したがって(私には)ほとんど役に立ちません。さらに、直接ダウンロードリンクが見つからないようです。また、サポートされている最小のSSEバージョンも記載されていません。

また、いくつかのベクトル化された文字列関数(strlen、strstr strcmp)に関する記事に出くわしました。ただし、SSE4.2は私の手の届かないところにあります(前に述べたように、SSE1 / MMXに固執したいと思います)。

アップデート2

Paul Rは、少しベンチマークを行うように動機付けました。残念ながら、私のSSEアセンブリコーディングの経験はzipに近いため、他の誰か(http://www.mindcontrol.org/~hplus/)のベンチマークコードを使用して追加しました。完全/カスタマイズされた最適化を使用してVC9SP1でコンパイルされたすべてのテスト(元のVC6 SP5を除く)/arch:SSE

最初のテストは、SSE1でキャップされた私のホームマシン(AMD Sempron 2200+ 512mb DDR 333)の1つでした(したがって、MSVC memcpyによるベクトル化はありません):

comparing P-III SIMD copytest (blocksize 4096) to memcpy
calculated CPU speed: 1494.0 MHz
  size  SSE Cycles      thru-sse    memcpy Cycles   thru-memcpy     asm Cycles      thru-asm
   1 kB 2879        506.75 MB/s     4132        353.08 MB/s     2655        549.51 MB/s
   2 kB 4877        598.29 MB/s     7041        414.41 MB/s     5179        563.41 MB/s
   4 kB 8890        656.44 MB/s     13123       444.70 MB/s     9832        593.55 MB/s
   8 kB 17413       670.28 MB/s     25128       464.48 MB/s     19403       601.53 MB/s
  16 kB 34569       675.26 MB/s     48227       484.02 MB/s     38303       609.43 MB/s
  32 kB 68992       676.69 MB/s     95582       488.44 MB/s     75969       614.54 MB/s
  64 kB 138637      673.50 MB/s     195012      478.80 MB/s     151716      615.44 MB/s
 128 kB 277678      672.52 MB/s     400484      466.30 MB/s     304670      612.94 MB/s
 256 kB 565227      660.78 MB/s     906572      411.98 MB/s     618394      603.97 MB/s
 512 kB 1142478     653.82 MB/s     1936657     385.70 MB/s     1380146     541.23 MB/s
1024 kB 2268244     658.64 MB/s     3989323     374.49 MB/s     2917758     512.02 MB/s
2048 kB 4556890     655.69 MB/s     8299992     359.99 MB/s     6166871     484.51 MB/s
4096 kB 9307132     642.07 MB/s     16873183        354.16 MB/s     12531689    476.86 MB/s

完全なテスト

2番目のテストバッチは、大学のワークステーション(Intel E6550、2.33Ghz、2gb DDR2 800?)で実行されました。

VC9 SSE/memcpy/ASM:
comparing P-III SIMD copytest (blocksize 4096) to memcpy
calculated CPU speed: 2327.2 MHz
  size  SSE Cycles      thru-sse    memcpy Cycles   thru-memcpy     asm Cycles      thru-asm
   1 kB 392         5797.69 MB/s    434         5236.63 MB/s    420         5411.18 MB/s
   2 kB 882         5153.51 MB/s    707         6429.13 MB/s    714         6366.10 MB/s
   4 kB 2044        4447.55 MB/s    1218        7463.70 MB/s    1218        7463.70 MB/s
   8 kB 3941        4613.44 MB/s    2170        8378.60 MB/s    2303        7894.73 MB/s
  16 kB 7791        4667.33 MB/s    4130        8804.63 MB/s    4410        8245.61 MB/s
  32 kB 15470       4701.12 MB/s    7959        9137.61 MB/s    8708        8351.66 MB/s
  64 kB 30716       4735.40 MB/s    15638       9301.22 MB/s    17458       8331.57 MB/s
 128 kB 61019       4767.45 MB/s    31136       9343.05 MB/s    35259       8250.52 MB/s
 256 kB 122164      4762.53 MB/s    62307       9337.80 MB/s    72688       8004.21 MB/s
 512 kB 246302      4724.36 MB/s    129577      8980.15 MB/s    142709      8153.80 MB/s
1024 kB 502572      4630.66 MB/s    332941      6989.95 MB/s    290528      8010.38 MB/s
2048 kB 1105076     4211.91 MB/s    1384908     3360.86 MB/s    662172      7029.11 MB/s
4096 kB 2815589     3306.22 MB/s    4342289     2143.79 MB/s    2172961     4284.00 MB/s

完全なテスト

ご覧のとおり、SSEは私のホームシステムでは非常に高速ですが、Intelマシンでは機能しません(おそらくコーディングが不適切なためですか?)。私のx86アセンブリバリアントは、自宅のマシンで2番目、Intelシステムで2番目になります(ただし、結果は少し一貫性がないように見えます。1つの抱擁がSSE1バージョンを支配します)。MSVC memcpyは、手作業で行われたIntelシステムテストに勝ちます。これはSSE2のベクトル化によるものですが、私の自宅のマシンでは、ひどいものでさえも__movsdそれを打ち負かします...

落とし穴:メモリはすべて2の累乗で調整されていました。キャッシュは(うまくいけば)フラッシュされました。タイミングにはrdtscを使用しました。

興味深い点:MSVCには(どの参照にもリストされていない)__movsd組み込み関数があり、使用しているのと同じアセンブリコードを出力しますが、(インライン化されている場合でも)陰気に失敗します。それがおそらくその非公開の理由です。

VC9 memcpyは、sse 2以外のマシンで強制的にベクトル化することができますが、FPUスタックが破損しますが、バグもあるようです。

これは、私がテストに使用したものの完全なソースです(これも、オリジナルのhttp://www.mindcontrol.org/~hplus/へのクレジットを含みます)。プロジェクトファイルのバイナリは、リクエストに応じて利用できます。

結論として、MSVC crtのものと同様に、スイッチングバリアントが最適であるように思われますが、より多くのオプションと単一の1回限りのチェック(インライン関数ポインターを介して?または内部直接呼び出しのようなより悪質なものを介して)ではるかに頑丈ですパッチ)、ただし、インライン化はおそらく代わりに最良の方法を使用する必要があります

アップデート3

Eshanが尋ねた質問は、これに関連する有用なものを思い出させました。ビットセットとビット演算、BitMagicのみで、大きなビットセットに非常に役立ちますが、 SSE2(ビット)最適化に関するすばらしい記事もあります。残念ながら、これはまだCRT/stdlib風のタイプライブラリではありません。これらのプロジェクトのほとんどは、(問題の)特定の小さなセクションに専念しているようです。

これは疑問を投げかけます。オープンソースの、おそらくマルチプラットフォームのパフォーマンスcrt / stdlibプロジェクトを作成し、それぞれが特定の状況に最適化されたさまざまなバージョンの標準化された関数と「ベストケース」を作成することは価値がありますか? '/関数の汎用バリアント。スカラー/MMX/ SSE / SSE2 +(MSVC)のランタイム分岐または強制コンパイル時のスカラー/SIMDスイッチのいずれかを使用します。

これは、HPC、またはパフォーマンスのすべてのビットが重要なプロジェクト(ゲームなど)に役立つ可能性があり、プログラマーは組み込み関数の速度について心配する必要がなく、最適な最適化されたバリアントを見つけるためにわずかな調整が必要です。

アップデート4

この質問の性質を拡張して、SSE / MMXを使用して非ベクトル/マトリックスアプリケーションの最適化に適用できる手法を含める必要があると思います。これは、おそらく32/64ビットスカラーコードにも使用できます。良い例は、スカラー技術(ビット操作)、MMXおよびSSE / SIMDを使用して、特定の32/64/128/256ビットデータ型のバイトの発生を一度にチェックする方法です。

また、「ICCを使うだけ」という答えがたくさんありますが、それは良い答えです。まず、ICCは継続的に使用できるものではないため、私のような答えではありません(Intelに無料の学生版がない限り)Windowsの場合)、30回の試行のため。第二に、そしてより適切には、私はライブラリ自体だけでなく、ライブラリに含まれる関数を最適化/作成するために使用される技術を個人的な教育と改善のために使用しているので、そのような技術と原則を自分のコードに適用できます(必要に応じて)、これらのライブラリの使用と組み合わせて。うまくいけば、それはその部分をクリアします:)

4

8 に答える 8

2

SIMD 命令を使用して文字カウントをベクトル化する方法に関する記事を次に示します。

http://porg.es/blog/ridiculous-utf-8-character-counting

于 2010-10-06T10:52:01.063 に答える
1

memset、memcpyなど、計算がほとんどない単純な操作の場合、メモリ帯域幅が通常制限要因になるため、SIMD最適化にはほとんど意味がありません。

于 2010-10-05T10:52:53.750 に答える
1

多分libSIMDx86?

http://simdx86.sourceforge.net

于 2010-10-05T12:32:21.203 に答える
1

正直なところ、インテル C++ コンパイラーをインストールして、利用可能なさまざまな自動 SIMD 最適化フラグを学習するだけです。私たちは、コードを ICC でコンパイルするだけで、コードのパフォーマンスを最適化するという非常に良い経験を積んできました。

STL ライブラリ全体は基本的に単なるヘッダー ファイルであるため、すべてが exe/lib/dll にコンパイルされ、好きなように最適化できることに注意してください。

ICC には多くのオプションがあり、対象とする SSE レベルを (最も簡単に) 指定できます。これを使用して、複数のコード パスを含むバイナリ ファイルを生成することもできます。これにより、コンパイルした最適な SSE 構成が利用できない場合、能力の低い SIMD CPU 用に構成された (まだ最適化されている) コードの別のセットが実行されます。 .

于 2010-10-08T09:11:17.703 に答える
1

これは、必要に応じて memcpy の標準ライブラリ バージョンを置き換えることができる、C での高速な memcpy の実装です。

http://www.danielvik.com/2010/02/fast-memcpy-in-c.html

于 2010-10-08T09:44:01.667 に答える
1

strstr を最適化するのは困難です。なぜなら、(a) \0-termination はとにかくすべてのバイトを読み取る必要があることを意味し、(b) すべてのエッジ ケースでも適切でなければならないからです。

そうは言っても、SSE2 ops を使用すると、標準の strstr を 10 倍上回ることができます。私は、gcc 4.4 がこれらの ops を現在 strlen に使用しているが、他の文字列 ops には使用していないことに気付きました。strlen、strchr、strpbrk などに SSE2 レジスタを使用する方法の詳細については、mischasan.wordpress.com を参照してください。私の非常に簡潔なコードレイアウトを許してください。

#include <emmintrin.h> // Other standard #includes you can figure out...

static inline unsigned under(unsigned x)
    { return (x - 1) & ~x; }
static inline __m128i xmfill(char b)
    { return _mm_set1_epi8(b); }
static inline __m128i xmload(void const*p)
    { return _mm_load_si128((__m128i const*)p); }
static inline unsigned xmatch(__m128i a, __m128i b)
    { return _mm_movemask_epi8(_mm_cmpeq_epi8(a, b)); }

char const *sse_strstr(char const *tgt, char const *pat)
{
    unsigned    len = sse_strlen(pat);
    if (len == 0) return tgt;
    if (len == 1) return sse_strchr(tgt,*pat);
    __m128i     x, zero = {};
    __m128i     p0 = _m_set1_epi8(pat[0]), p1 = _m_set1_epi8(pat[1]);
    uint16_t    pair = *(uint16_t const*)pat;
    unsigned    z, m, f = 15 & (uintptr_t)tgt;
    char const* p;

    // Initial unaligned chunk of tgt:
    if (f) {
        z = xmatch(x = xmload(tgt - f), zero) >> f;
        m = under(z) & ((xmatch(x,p0) & (xmatch(x,p1) >> 1)) >> f);
        for (; m; m &= m - 1)
             if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
                return p;
        if (z)
            return NULL;
        tgt += 16 - f;
        if (*(uint16_t const*)(tgt - 1) == pair
                && !memcmp(tgt+1, pat+2, len-2))
            return tgt - 1;
    }

    // 16-byte aligned chunks of tgt:
    while (!(z = xmatch(x = xmload(tgt), zero))) {
        m = xmatch(x,p0) & (xmatch(x,p1) >> 1);
        for (; m; m &= m - 1)
             if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
                return p;
        tgt += 16;
        if (*(uint16_t const*)(tgt - 1) == pair && !memcmp(tgt+1, pat+2, len-2))
            return tgt - 1;
    }

    // Final 0..15 bytes of tgt:
    m = under(z) & xmatch(x,p0) & (xmatch(x,p1) >> 1);
    for (; m; m &= m - 1)
        if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
            return p;

    return NULL;
}
于 2011-09-18T00:45:54.223 に答える
1

Apple または OpenSolaris の libc を使用できます。これらの libc 実装には、探しているものが含まれています。私はこの種のものを約6年前に探していましたが、苦労してそれを書かなければなりませんでした.

何年も前に、「 fastcode」プロジェクトと呼ばれるコーディング コンテストをフォローしたことを覚えています。彼らは当時、Delphi を使用して画期的な最適化を行いました。結果ページをご覧ください。Pascal の高速関数呼び出しモデル (引数をレジスタにコピーする) で記述されているため、C スタイルの stdc 関数呼び出しモデル (スタックにプッシュする) に変換するのは少し面倒かもしれません。このプロジェクトは長い間更新されておらず、特に SSE4.2 用のコードは書かれていません。

Solaris -> src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libc/

Apple -> www.opensource.apple.com/source/Libc/Libc-594.9.1/
于 2010-10-05T15:05:15.610 に答える
0

私は個人的に、すべての可能なシナリオを良好なパフォーマンスで処理しようとするlibc関数の超最適化バージョンを作成しようとはしませんでした。

代わりに、適切なコードを書くために目前の問題について十分に知っている特定の状況に最適化されたバージョンを作成してください...そしてそれが重要な場合。との間には意味上の違いがmemsetありClearLargeBufferCacheWriteThroughます。

于 2010-10-08T09:00:50.927 に答える