Intel TSX HLE を触ってみた x86opti

5,105 views

Published on

Intel TSX HLE の可能性を探るべく,いくつかのマイクロベンチマークで性能評価した.

Published in: Technology

Intel TSX HLE を触ってみた x86opti

  1. 1. Intel TSX HLE を触ってみた x86/x64最適化勉強会6 2013-08-31 星野喬@サイボウズ・ラボ / @starpoz 1
  2. 2. 自己紹介 •  星野 喬(@starpoz) –  サイボウズ・ラボ •  興味 –  データベース,ストレージ,分散アルゴリズムなど •  今の仕事: #walbdev –  Linux における block device の効率的な 増分記録ドライバ •  http://developer.cybozu.co.jp/tech/?p=5130‎ 2
  3. 3. 今日の話 •  Intel の最新 CPU (Haswell) の 新機能のひとつ TSX HLE を使い 性能評価をしてみて トランザクショナルメモリの可能性を探る •  後追い実験大歓迎 J 3
  4. 4. トランザクショナルメモリ (TM) •  「Intel TSX について」参照 –  http://www.slideshare.net/starpos/intel-tsxx86opti4 •  要約: atomic メモリアクセスを実現する技術 –  他と競合したくない一連のメモリアクセス –  Lock-free な時代がすぐそこに(反論アリ 4
  5. 5. Intel TSX •  Transactional Synchronization eXtension •  2 つのインターフェースを提供 –  HLE: Lock prefix の拡張で細粒度の排他を実現 –  RTM: 制約はあるが HardwareTM そのもの •  楽観的な振舞 •  CPU L1 キャッシュ上で必要なデータを管理 •  キャッシュライン単位での競合検出 5
  6. 6. 競合 (collision) •  あるリソースへの複数主体からのアクセスが 同時に起きること(要出典) •  Intel TSX の場合: –  リソース: キャッシュライン単位のメモリ領域 –  主体: スレッド –  アクセス: read/write (全て read の場合を除く) –  同時: クリティカルセクションの実行期間が重複 6
  7. 7. HLE: Hardware Lock Elision 競合発生しないケース 競合発生するケース 時間 ロックを無視して 投機実行開始 競合発生しなかった めでたしめでたし 時間 競合発生 ロック取れたので 変更破棄 必ず実行できる ロック待ち体制 •  楽観的挙動 –(失敗)à 悲観的挙動 •  失敗すると電気代が無駄になる 7
  8. 8. Spinlock with HLE •  GCC 4.8 の場合 (マニュアル参照) class SpinlockHle { private: char &lock_; public: explicit SpinlockHle(char &lock) : lock_(lock) { int flags = __ATOMIC_ACQUIRE | __ATOMIC_HLE_ACQUIRE; while (__atomic_exchange_n(&lock_, 1, flags)) _mm_pause(); } ~SpinlockHle() noexcept { int flags = __ATOMIC_RELEASE | __ATOMIC_HLE_RELEASE; __atomic_clear(&lock_, flags); } }; 8
  9. 9. HLE on/off の違い --- t1.hle0.s 2013-08-31 09:23:58.000000000 +0900 +++ t1.hle1.s 2013-08-31 09:23:30.000000000 +0900 @@ -13,12 +13,12 @@ .L3: rep nop .L2: movl %edx, %eax xchgb -1(%rsp), %al + xacquire xchgb -1(%rsp), %al testb %al, %al jne .L3 movb $0, -1(%rsp) + xrelease movb $0, -1(%rsp) xorl %eax, %eax ret .cfi_endproc .LFE859: 9
  10. 10. 実験環境 •  CPU: Core i7-4770 –  4cores 8HT –  HT 有効 –  TurboBoost 有効 •  メモリ: 16GB •  OS: Ubuntu 13.04 x86_64 kernel 3.8.19 •  コンパイラ: GCC 4.8.1 –  最適化フラグ: -O2 のみ 10
  11. 11. 実験方法 •  スレッドを必要なだけ起動して,X 秒間クリティカル セクション (CS) を繰り返し実行 •  Spinlock を使って CS の排他を取る –  HLE 有効/無効は実験パラメータ –  終了判定のために CS 実行毎に atomic<bool> を load するオーバーヘッドあり •  CS の実行回数の合計値を X で割ったものを スループットとする •  上記実験を Y 回行い,スループットの avg, min, max を計算 11
  12. 12. Experiments •  •  •  •  •  Expr1: Expr2: Expr3: Expr4: Expr5: simple counter(s) counter(s) with delay collision timing access area size map operations 12
  13. 13. Expr1: simple counter(s) •  クリティカルセクションループ while (!isEnd_.load(std::memory_order_relaxed)) { SpinlockHle<useHLE> lk(mutex_); counter_++; } •  パラメータ –  競合率 100%: counter を全スレッドで共有 –  競合率 0%: スレッド毎に counter を持つ •  キャッシュラインが異なるように 64byte 毎に配置 Counter Thread 競合率 100% Thread 競合率 0% Counter 13
  14. 14. Expr1: result 44.7x 10 sec, 20 trials spinlock なしの 8 threads 実行で 4309M なので, isEnd.load() のオーバーヘッドは 8% 程度 14
  15. 15. Expr1: result (scaled) 競合100% のときは オーバーヘッドの分 性能悪化 10 sec, 20 trials 15
  16. 16. Expr2: counter(s) with delay •  1us delay (誤差は実測 70ns 以下) void delayUsec(uint64_t usec) { auto ts0 = std::chrono::high_resolution_clock::now(); uint64_t diff = 0; while (diff < usec * 1000) { auto ts1 = std::chrono::high_resolution_clock::now(); diff = std::chrono::duration_cast< std::chrono::nanoseconds>(ts1 - ts0).count(); } } •  クリティカルセクション while (!isEnd_.load(std::memory_order_relaxed)) { SpinlockHle<useHLE> lk(mutex_); counter_++; delayUsec(1); } 16
  17. 17. Expr2: result 10 sec, 20 trials cs が 1us でも最大 5 倍程度の性能アップを期待できる 17
  18. 18. Expr3: •  1us delay の前/後にカウンタを更新する while (!isEnd_.load(std::memory_order_relaxed)) { SpinlockHle<useHLE> lk(mutex_); if (isBefore) delayUsec(1); counter_++; if (!isBefore) delayUsec(1); } •  目的 –  競合発覚するのが CS の最初か最後かで楽観的 実行が失敗する頻度が変化するのを観察したい 18
  19. 19. Expr3: result 10 sec, 20 trials 19
  20. 20. Expr4: •  目的 –  write buffer や read flag を管理する領域は有 限なので,HLE で恩恵が受けられるアクセスサイ ズの上限を知りたい •  手段 –  X 個の 64bytes メモリ断片をスレッド毎に用意 –  CS の中で Y 個にアクセスする(重複アリ) –  他の条件を同じにするため,Y は固定 –  2 <= X <= Y で評価 –  今回は write の評価のみ 20
  21. 21. Expr4: 1-4 thread 16 clines 1 threads 3 threads 10 sec, 20 trials 2 threads 4 threads CS 内では 12 lines までのアクセスにした方が良さそう 21
  22. 22. Expr4: 5-8 thread 16 clines 5 threads 7 threads 10 sec, 20 trials 6 threads 8 threads 安定しない結果.12 lines を越えても効果があるケースも 22
  23. 23. Expr4: 5-8 thread 64 clines 5 threads 7 threads 5 sec, 100 trials 6 threads 8 threads 安定しない結果,実験方法に問題があるかも? 23
  24. 24. Expr5: •  目的: –  実用的なデータ構造で HLE の効果を知る •  手段: –  std::map<uint32_t, uint32_t> をひとつの spinlock で排他 –  read 比率を変える: 0%, 90%, 99%, 100% •  read 操作: ランダムキーで lower_bound 検索 •  write 操作: ひとつ削除,その後 insert –  初期アイテム数: 10K (約2MB), 1M (約100MB) –  (ついでに自作の btree map でも試す) 24
  25. 25. 20131022追記 •  以下 expr5 の結果グラフのスループットは全 て誤って 3 倍に集計されていたことが発覚 –  集計スクリプトのミス •  スループットを見るときは表記の 1/3 にして ご覧ください 25
  26. 26. Expr5: 10K items, read 0% std::map はスレッド数増加で HLE on が逆転 性能上昇は最大で 39% (8 threads) btree も同様の傾向 26
  27. 27. Expr5: 10K items, read 90% std::map では 2 threads 以上で HLE on が上回る. 最大46%性能Up (3 threads) btree では傾向が安定しないが 概ね同程度と言える 27
  28. 28. Expr5: 10K items, read 99% std::map は最大 2.9 倍の性能 (3 threads) btree も最大 2.4 倍の性能 (3 threads) 28
  29. 29. Expr5: 10K items, read 100% std::map は 6 threads で 7.4 倍 btree は 4 threads で 3.4 倍 29
  30. 30. Expr5: 1M items, read 0% 3 threads 以上で HLE on の方が良い std::map 6.9% up (3 threads) btree: 7.3% up (3 threads) 30
  31. 31. Expr5: 1M items, read 90% std::map は 58% up (3 threads) btree は 54% up (3 threads) 31
  32. 32. Expr5: 1M items, read 99% std::map は 2.4 倍 (3 threads) btree は 2.4 倍 (3 threads) 32
  33. 33. Expr5: 1M items read 100% std::map は 2.6 倍 (4 threads) btree は 2.6 倍 (3 threads) 33
  34. 34. Expr5: まとめ •  性能向上 –  10K items で 最大 7.4 倍 (read 100%) –  1M items で 最大 2.6 倍 (read 100%) •  現状の結論 –  データがキャッシュに乗る程度に小さく read 比 率が低いと HLE のオーバーヘッドが目立つ •  考察 –  critical section でより多くの操作をするケースも 評価すべき 34
  35. 35. HLE 評価まとめ •  手間なしで性能が向上する魔法 –  楽観的挙動 à 悲観的挙動なので 性能最悪値を保証してくれるのも魅力 –  デッドロックは従来通り気をつける必要あり •  使うべき条件 –  条件1: 競合が起きにくい (read 比率が高い) –  条件2: クリティカルセクション実行時間が短い –  条件3: アクセス対象メモリが少ない 35
  36. 36. 今後の展望 •  HLE には条件分岐予測のように elision すべきかど うかを予測して性能向上させる余地があるのではな いか? –  今回あまり調べてないので既にやってたらごめんなさい •  RTMは? –  L1 のみならず,L2/L3 そしてメモリコントローラまで TM のことを考えてくれるようになるまで様子見したい –  速度が重要じゃない用途なら STM と連動できるように なった時点で開発効率の点から有用なのではないか 36
  37. 37. おまけ: 私の単体 CPU 購入歴 •  •  •  •  •  •  AMD K6-300 Intel Pentium II 333MHz AMD Athlon 64 3200+ AMD Athlon X2 BE-2400 AMD Phenom II X4 910e AMD Phenom II X6 1065T •  こんな私が買う気になってるのだから Haswell は凄い! 37
  38. 38. 実験してみたい人へ •  用意するもの –  TSX サポート付きの Haswell CPU –  Linux OS (古いものはオススメしない) –  GCC 4.8 以降 •  ソースコード –  https://github.com/starpos/hle_bench 38
  39. 39. ありがとうございました •  ご質問,コメントはご気軽にどうぞ J 39

×