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

[x86/x64最適化勉強会4] 「AMDで使うと遅いんだけど」 / [x86 x64 opt...

[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. 自己紹介 • 映像可逆圧縮コーデック Ut Video Codec Suite の作者 ※ http://umezawa.dyndns.info/wordpress/?cat=28

    • ある2ちゃんねらー曰く、 UtVideo唯一の欠点 作者がニコ厨 ※ http://pc11.2ch.net/test/read.cgi/avi/1205486331/178 – まったくツンデレなんだから…
  2. あるユーザの報告 • 「AMD で ULRG や ULRA を使うとエンコードが すごい遅いんだけど」 –

    ULRG は内部保持形式が RGB 8bpc のもの。 ULRA は同じく RGBA 8bpc のもの。 – ULY2 (YUV422 8bpc) や ULY0 (YUV420 8bpc) は遅くないらしい。 • デコードはエンコードほどではないが、やっぱ り遅いことは遅いらしい。
  3. 実測 • 確かに遅い。 • ULRG は 24bpp であり、16bpp である ULY2

    と 比較して同じ画像サイズの時 1.5 倍ぐらい遅 いことが期待されるが、エンコードの場合は 期待されるより3倍ぐらい遅い。 明らかに何かおかしい
  4. エンコーダの実装 • 以下の順序で処理する。 – Packed → Planar 変換 – フレーム内予測

    – ハフマン符号化 • フレーム内予測とハフマン符号化は種類によ らず全く同じ処理なので、Planar 変換に問題 がありそう。 – 本来は全体の 1 割ぐらいの時間なんだけど…
  5. 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; }
  6. ちょっと変えてみる…速度変わらず 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; }
  7. さらに変えてみる…やっぱり遅い 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; }
  8. 遅くなくなった!? 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++; }
  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]; *(r++) = 0; }
  10. 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]; }
  11. VirtualAlloc() • 呼び出しプロセスのアドレス空間を予約ある いはコミットする。 – POSIX の mmap() に似ている。 •

    予約あるいはコミットするアドレスは「割り当て 粒度 (allocation granularity)」に丸められる。 – ページサイズ (=4KiB) ではない。 – 少なくとも Windows XP~7 においては、Win32 で の割り当て粒度は 64KiB である。
  12. AMD の L1 キャッシュ • 長らく 命令 64KiB + データ

    64KiB の構成 • 長らく 2-way セットアソシアティブ • → 32KiB ごとに同じエントリアドレスになる。
  13. 両方合わせると… • VirtualAlloc() で割り当てられたバッファは 64KiB 境界に整列しているので、各バッファの 先頭アドレスは全て同じエントリアドレスを持 つ。 • ULRG

    では g, b, r のポインタが同じ速度で進 み「常に」同じエントリアドレスになるため、1 バイトアクセスするたびにキャッシュミスして 猛烈に遅くなる。
  14. これで解決 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 でいいかどうかは議論(というか計測)の余地がある。
  15. 当時(あまり)考えなかったこと • L1 キャッシュを共有する複数の物理スレッド – Intel HT とかのことだが、Intel 系だと 8-way

    なの で、2 スレッド走っても 1 スレッドあたり 4-way で 問題なし。 – AMD Bulldozer の場合、L1 は Bulldozer モジュー ルごとではなくコアごとに持ってるらしいから、半 分にはならない?