Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[x86/x64最適化勉強会4] 「AMDで使うと遅いんだけど」 / [x86 x64 optimization study 4] Ut Video Codec Suite is slow on AMD processors

[x86/x64最適化勉強会4] 「AMDで使うと遅いんだけど」 / [x86 x64 optimization study 4] Ut Video Codec Suite is slow on AMD processors

x86/x64最適化勉強会4 (https://atnd.org/events/28847) で発表した、 Ut Video Codec Suite (https://github.com/umezawatakeshi/utvideo) が AMD のプロセッサで動かすと遅い件を調査した結果のプレゼンテーションです。

More Decks by 梅澤威志 / UMEZAWA Takeshi

Other Decks in Technology

Transcript

  1. 「AMDで使うと遅いんだけど」
    x86/x64最適化勉強会 #4 LT
    梅澤威志 (UMEZAWA Takeshi)
    @umezawa_takeshi

    View full-size slide

  2. Q: dis ってんの?
    A: disasm なら少々…

    View full-size slide

  3. 自己紹介
    • 映像可逆圧縮コーデック
    Ut Video Codec Suite の作者
    ※ http://umezawa.dyndns.info/wordpress/?cat=28
    • ある2ちゃんねらー曰く、
    UtVideo唯一の欠点
    作者がニコ厨
    ※ http://pc11.2ch.net/test/read.cgi/avi/1205486331/178
    – まったくツンデレなんだから…

    View full-size slide

  4. 前置き
    • 今回話すことは、何人かの人は過去の
    x86/x64最適化勉強会で雑談などで既に聞い
    ているはずです。
    • blog を検索しても出てきます。
    • 知ってる人は寝てていいです。

    View full-size slide

  5. あるユーザの報告
    • 「AMD で ULRG や ULRA を使うとエンコードが
    すごい遅いんだけど」
    – ULRG は内部保持形式が RGB 8bpc のもの。
    ULRA は同じく RGBA 8bpc のもの。
    – ULY2 (YUV422 8bpc) や
    ULY0 (YUV420 8bpc) は遅くないらしい。
    • デコードはエンコードほどではないが、やっぱ
    り遅いことは遅いらしい。

    View full-size slide

  6. 実測
    • 確かに遅い。
    • ULRG は 24bpp であり、16bpp である ULY2 と
    比較して同じ画像サイズの時 1.5 倍ぐらい遅
    いことが期待されるが、エンコードの場合は
    期待されるより3倍ぐらい遅い。
    明らかに何かおかしい

    View full-size slide

  7. エンコーダの実装
    • 以下の順序で処理する。
    – Packed → Planar 変換
    – フレーム内予測
    – ハフマン符号化
    • フレーム内予測とハフマン符号化は種類によ
    らず全く同じ処理なので、Planar 変換に問題
    がありそう。
    – 本来は全体の 1 割ぐらいの時間なんだけど…

    View full-size slide

  8. Planar 変換
    r = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    g = (ditto)
    b = (ditto)
    for (p = srcbegin; p < srcend; p += 3) {
    *(g++) = p[1];
    *(b++) = p[0] - p[1] + 0x80;
    *(r++) = p[2] - p[1] + 0x80;
    }

    View full-size slide

  9. ちょっと変えてみる…速度変わらず
    r = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    g = (ditto)
    b = (ditto)
    for (p = srcbegin; p < srcend; p += 3) {
    *(g++) = p[1];
    *(b++) = p[0] - p[1]; // + 0x80;
    *(r++) = p[2] - p[1]; // + 0x80;
    }

    View full-size slide

  10. さらに変えてみる…やっぱり遅い
    r = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    g = (ditto)
    b = (ditto)
    for (p = srcbegin; p < srcend; p += 3) {
    *(g++) = p[1];
    *(b++) = p[0]; // - p[1] + 0x80;
    *(r++) = p[2]; // - p[1] + 0x80;
    }

    View full-size slide

  11. 遅くなくなった!?
    r = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    g = (ditto)
    b = (ditto)
    for (p = srcbegin; p < srcend; p += 3) {
    *(g++) = p[1];
    *(b++) = p[0];
    r++;
    }

    View full-size slide

  12. 対照群:遅いまま
    r = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    g = (ditto)
    b = (ditto)
    for (p = srcbegin; p < srcend; p += 3) {
    *(g++) = p[1];
    *(b++) = p[0];
    *(r++) = 0;
    }

    View full-size slide

  13. ULY2 の場合(遅くない)
    y = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    u = VirtualAlloc(NULL, width * height / 2,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    v = (ditto)
    for (p = srcbegin; p < srcend; p += 4) {
    *(y++) = p[0];
    *(u++) = p[1];
    *(y++) = p[2];
    *(v++) = p[3];
    }

    View full-size slide

  14. Q: なぜこうなるのでしょう?

    View full-size slide

  15. A: store で毎回 L1 キャッシュミス
    するから

    View full-size slide

  16. VirtualAlloc()
    • 呼び出しプロセスのアドレス空間を予約ある
    いはコミットする。
    – POSIX の mmap() に似ている。
    • 予約あるいはコミットするアドレスは「割り当て
    粒度 (allocation granularity)」に丸められる。
    – ページサイズ (=4KiB) ではない。
    – 少なくとも Windows XP~7 においては、Win32 で
    の割り当て粒度は 64KiB である。

    View full-size slide

  17. AMD の L1 キャッシュ
    • 長らく 命令 64KiB + データ 64KiB の構成
    • 長らく 2-way セットアソシアティブ
    • → 32KiB ごとに同じエントリアドレスになる。

    View full-size slide

  18. 両方合わせると…
    • VirtualAlloc() で割り当てられたバッファは
    64KiB 境界に整列しているので、各バッファの
    先頭アドレスは全て同じエントリアドレスを持
    つ。
    • ULRG では g, b, r のポインタが同じ速度で進
    み「常に」同じエントリアドレスになるため、1
    バイトアクセスするたびにキャッシュミスして
    猛烈に遅くなる。

    View full-size slide

  19. 解決方法
    • ポインタが同じ速度で進むのだから、最初か
    らずらしておけば今度は絶対に同じエントリア
    ドレスにはならない。
    • p は 3 倍速で進むのでエントリアドレスが重な
    ることがあるが、その時でも同じエントリアドレ
    スを使っているのは 2 つだけなのでセーフ。

    View full-size slide

  20. これで解決
    r = VirtualAlloc(NULL, width * height,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE);
    g = VirtualAlloc(NULL, width * height + 256,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE) + 256;
    b = VirtualAlloc(NULL, width * height + 512,
    MEM_COMMIT|MEM_RESERVE,
    PAGE_READWRITE) + 512;

    ※ 256 でいいかどうかは議論(というか計測)の余地がある。

    View full-size slide

  21. 当時(あまり)考えなかったこと
    • L1 キャッシュを共有する複数の物理スレッド
    – Intel HT とかのことだが、Intel 系だと 8-way なの
    で、2 スレッド走っても 1 スレッドあたり 4-way で
    問題なし。
    – AMD Bulldozer の場合、L1 は Bulldozer モジュー
    ルごとではなくコアごとに持ってるらしいから、半
    分にはならない?

    View full-size slide

  22. まとめ?
    • キャッシュの連想度にも(たまには)気を付け
    ましょう。
    • でも 2-way はひどいと思います。
    – Intel は 8-way なのに。

    View full-size slide

  23. Q: 結局 x86 関係あんの?
    A: さあ…?

    View full-size slide