Slide 1

Slide 1 text

ビットボード uc

Slide 2

Slide 2 text

今日話すこと • 前置き • 高速化について • ビット演算基礎 • 基本セルオートマトン • オセロビットボード • チェスビットボード

Slide 3

Slide 3 text

なぜビットボード? • ボードゲームなどのAIを作るには大量の手の探索が必要 • ゲームの手番処理を高速化できれば探索数を増やせる • 手番処理の高速化に使える手法がビットボード

Slide 4

Slide 4 text

扱う数について • 扱う数はすべて符号なし整数 (unsigned integer) • 基本的には64ビット整数を想定

Slide 5

Slide 5 text

n進数 • 2進数 • 基本的に断りのない数は2進数 • ほかの進数と区別するときは接頭辞 "0b" を付ける • 16進数 • 64ビット整数などの大きな数を書くときに使用 • 接頭辞 "0x" を付ける

Slide 6

Slide 6 text

コードについて • 提示するコードは簡略化のためPythonで示す • Pythonの整数型は符号ありで上限を持たないが、 ここでは符号なし64ビット整数であるとして扱う ( でマスクすればよい) • 引数チェックなどは省く

Slide 7

Slide 7 text

高速化 プログラムを高速化する手法

Slide 8

Slide 8 text

高速化 • 速いマシンを使う • 速い言語を使う • オーダーが小さいアルゴリズムにする • ルックアップテーブルを作る • キャッシュ効率を上げる • 並列化する • 命令数を減らす

Slide 9

Slide 9 text

ルックアップテーブル • 三角関数などの引数と戻り値の対応表をあらかじめ作っておき、 プログラム実行時にはマクローリン展開などの計算をせずに テーブル参照で戻り値を求める • 計算過程で出てきた再利用できる値をテーブルに保存しておく (メモ化)

Slide 10

Slide 10 text

並列化 • マルチプロセッサ • GPGPU • マルチスレッド • SIMD • ビット配列 ビットボードはビット配列を利用した高速化技術

Slide 11

Slide 11 text

ビット演算基礎

Slide 12

Slide 12 text

ビット演算 • 2進数の各ビットごとに論理演算を行う • 64ビット整数であれば64個の論理演算が同時に行われる

Slide 13

Slide 13 text

ビット演算 ※RustのビットNOTは 、GoのビットNOTは

Slide 14

Slide 14 text

シフト演算

Slide 15

Slide 15 text

演算子の優先順位 • • •

Slide 16

Slide 16 text

ビットマスク • 整数の特定のビットの情報だけを取り出したいときに、 特定のビットだけを1にした定数(マスク)と ビット演算することで情報を取り出す手法 • 例: IPv4アドレスのサブネットマスク

Slide 17

Slide 17 text

ビット配列 • 整数を各要素が1ビットの配列と見立てたもの • 64ビット整数なら長さ64のビット配列と見立てられる • ビットボードの基本となる概念 • ここから基本セルオートマトンを例にして解説していく

Slide 18

Slide 18 text

基本セルオートマトン ビット配列を用いた高速化の例 Copyright (c) 2005 Richard Ling

Slide 19

Slide 19 text

基本セルオートマトン • 横一列に無限にセルが並んでいる • それぞれのセルは各時刻において1か0かの状態をとる • 現在の自身と両隣のセルの状態をルールに照らし合わせて、 次の時刻のセルの状態が決められる … 0 1 0 0 1 1 0 1 1 1 … … 1 1 0 0 0 1 1 0 1 0 …

Slide 20

Slide 20 text

ウルフラムコード • 次のセルの状態を決めるのは3つの近傍セルの状態 • 3つの近傍セルの状態の組み合わせは23 = 8通り • 8通りの近傍セルの状態それぞれについて、次のセルの状態が 2通りあるので、ルールは28 = 256通り • 256通りのルールをナンバリングしたものがウルフラムコード • 例えば、下のルールのウルフラムコードは01011010 (2) = 90 現在の近傍 111 110 101 100 011 010 001 000 次の状態 0 1 0 1 1 0 1 0

Slide 21

Slide 21 text

ルール90 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 現在の近傍 111 110 101 100 011 010 001 000 次の状態 0 1 0 1 1 0 1 0

Slide 22

Slide 22 text

ルール90 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 現在の近傍 111 110 101 100 011 010 001 000 次の状態 0 1 0 1 1 0 1 0

Slide 23

Slide 23 text

ルール90 0 0 1 0 1 0 1 0 1 0 0 現在の近傍 111 110 101 100 011 010 001 000 次の状態 0 1 0 1 1 0 1 0 0 0 0 1 0 0 0 1 0 0 0

Slide 24

Slide 24 text

ルール90 0 1 0 0 0 0 0 0 0 1 0 現在の近傍 111 110 101 100 011 010 001 000 次の状態 0 1 0 1 1 0 1 0 0 0 1 0 1 0 1 0 1 0 0

Slide 25

Slide 25 text

ルール90 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

Slide 26

Slide 26 text

ルール90の意味 • 両隣が異なるなら1になり、同じなら0になる 現在の近傍 111 110 101 100 011 010 001 000 次の状態 0 1 0 1 1 0 1 0

Slide 27

Slide 27 text

ルール90を実装する|配列版

Slide 28

Slide 28 text

ルール90を実装する|ビット配列版

Slide 29

Slide 29 text

ビット配列の利点 • 整数のビット数の数だけまとめて計算することができる • まとめた分だけ計算量を減らせる • コードの記述量が少なくて済む

Slide 30

Slide 30 text

ビット配列の欠点 • コードの可読性が低い • 演算は基本的にビット演算 • 1つの要素には1変数あたり1ビットしか割り当てられない • 要素数がビット数を超える場合には分割が必要

Slide 31

Slide 31 text

ビットボードでオセロ 合法手の探索、石の数え上げ

Slide 32

Slide 32 text

ビットボード • オセロ、チェス、将棋などの盤面をビット配列で表す手法 • 64ビット整数の場合、オセロ・チェス(8x8)なら1個の変数、 将棋(9x9)なら2個の変数で盤面のフラグを管理できる

Slide 33

Slide 33 text

ビットボードでオセロ • マスの状態は「空」、「白石」、「黒石」の3通り • 「白石があるか」、「黒石があるか」の2つの64bit整数で盤面 を表せる • 「手番 (player)」と「相手 (opponent)」の方が実装上は便利 • 今回はblackとwhiteの2変数で説明

Slide 34

Slide 34 text

初期配置をビットボードで表す ● ● ● ●

Slide 35

Slide 35 text

初期配置をビットボードで表す ● ● ● ●

Slide 36

Slide 36 text

空白マスを求める ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 37

Slide 37 text

黒石が置けるマス(合法マス)を求めたい ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● 合法マスの定義: 1. 周囲8方向のいずれかにおいて 1個以上の白石が続いたのちに 黒石があるような空のマス 2. 黒石の周囲8方向にあって 1個以上の白石が続いたのちに ある空のマス

Slide 38

Slide 38 text

黒石の左方向にある合法マスを求めたい a b c d e f g h 1 2 3 ● ● ● ● 4 ● ● ● ● ● 5 ● ● ● ● ● ● ● 6 7 8 • 左図におけるa5のマスが 求めたい合法マス • 少し複雑な手順になるので ステップごとに説明

Slide 39

Slide 39 text

黒石の左にあるマスを求める ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 40

Slide 40 text

端を超えてしまった部分をマスクする ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 41

Slide 41 text

黒石の左にある白石を求める ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 42

Slide 42 text

さらに左にある白石を追加する ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 43

Slide 43 text

合計6回繰り返す ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 44

Slide 44 text

黒石の左方向にある合法マスを求める ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

Slide 45

Slide 45 text

黒石の左方向にある合法マスを求める

Slide 46

Slide 46 text

他の方向についても同様に求める • 上下方向は8ビットシフトすればOK • 上下方向、斜め方向は対応する別のマスクを使う必要がある

Slide 47

Slide 47 text

石の数を数える • 最終局面での盤面評価は石の数による • 石の数のカウントもなるべく高速化したい

Slide 48

Slide 48 text

石の数を数える(素朴な計算)

Slide 49

Slide 49 text

石の数を数える(高速)

Slide 50

Slide 50 text

何をやっているのか • 基本的なアイデアは分割統治法 0 0 1 0 0 1 0 1 1 1 1 1 1 0 1 1 0 1 1 1 2 2 1 2 1 2 4 3 3 7 10

Slide 51

Slide 51 text

• • •

Slide 52

Slide 52 text

• •

Slide 53

Slide 53 text

• • •

Slide 54

Slide 54 text

• • • •

Slide 55

Slide 55 text

• 下7ビットだけをマスクで取り出して返す

Slide 56

Slide 56 text

popcnt • IntelのSIMD拡張命令セット "SSE4.2" でpopcnt命令が追加さ れた • 自分でpopcountを書くより2倍くらい速い

Slide 57

Slide 57 text

ビットボードでチェス 遠方駒の利き判定

Slide 58

Slide 58 text

ビットボードでチェス • 全ての駒の位置をまとめたビットボード (occupied bitboard)、 駒種別の位置・利き・チェック位置など大量のビットボードを 管理 • すべてをビットボードで処理するわけではなく、盤面処理に 応じて配列とビットボードを使い分ける

Slide 59

Slide 59 text

遠方駒のやっかいさ • ルーク♜、ビショップ♝、クイーン♛などの遠方駒の利きは 他の駒の位置に影響される • 計算が煩雑な上に盤面が変わるたびに更新する必要がある • 遠方駒の利きを効率的に計算するためにoccupied bitboardが 使われる ♜ ♛ ♝

Slide 60

Slide 60 text

ルーク♜の横利きを求める ♙ ♜ ♞ ♚ • 左図のルーク♜の横利きを求 めることを考える

Slide 61

Slide 61 text

ルーク♜の横利きを求める 1 1 0 0 1 1 • 利きに関係する部分の occupied bitboardを ビットシフトとマスクで 取り出す

Slide 62

Slide 62 text

ルーク♜の横利きを求める ♙ ♜ ♞ ♚ • 取り出したOccupied Bitboardをキーとして、 ルークの横利きを格納した ルックアップテーブルを 参照して横利きを求める

Slide 63

Slide 63 text

ルーク♜の縦利き・ビショップ♝の利き • 横方向と異なり、縦方向や斜め方向の列はビットシフトと マスクだけで取り出すことはできない

Slide 64

Slide 64 text

Rotated bitboard • 縦方向や斜め方向の利きを算出するために、90度回転、±45度 回転させたoccupied bitboard (rotated bitboard) を管理する • 4つのoccupied bitboardを管理するので局面更新の処理が 増加する • 1つの変数に複数のrotated bitboardを詰め込めば 処理増加は抑えられる

Slide 65

Slide 65 text

Magic bitboard 6 5 4 3 2 1 g f e d c b ♜ • ルーク♜が右下にあるときの 利きに関係するマスは左の ようになる • Occupied bitboardから、 利きに関係するマスを残して 関係ないマスは0にマスクす る

Slide 66

Slide 66 text

Magic bitboard 6 5 4 3 2 1 g f e d c b ♜ • ここで、おもむろに を 左のマスクされたoccupied bitboardにかける

Slide 67

Slide 67 text

Magic bitboard g' f' e' d' c b 6 5 4 3 2 1 • すると、うまい具合に利きに 関係するマスが連続する • ただし、 • あとは横利きを求める場合と 同様に、利きに関係するマス の状態をキーとしたルック アップテーブルを参照すれば 縦横の利きが同時に求められ る g' f' e' d' = g f e d + 5 4 3 2

Slide 68

Slide 68 text

Magic bitboard • Occupied bitboardにかけるマジックナンバーは駒の種類と 位置に対応している • 利きに関係ないマスを0にマスクしておりボードは疎なので、 マジックナンバーの存在は十分に期待できる • マジックナンバーを解析的に求めることはできない • 乱数を使って総当り的にマジックナンバーを求める

Slide 69

Slide 69 text

pext • x86拡張命令セット "Bit manipulation instructions sets (BMI sets)" にて "pext (Parallel bits extract)" 命令が使えるように • 集めたいビットだけを立てたマスクを指定すれば、入力から指 定したビットを下位ビットに集めて出力してくれる • 入力がstuvwxyz、マスクが10101010なら出力は0000suwy、と いう具合

Slide 70

Slide 70 text

Rotated vs. Magic vs. pext • Rotated bitboardはテーブルが小さくて済む • Intel系ではpextが少し速い • ZENアーキテクチャではpextはかなり遅い • 今はMagic bitboardが主流

Slide 71

Slide 71 text

ビットボードで将棋 • 9x9 = 81マスあるので64ビット整数1つでは管理できない • 分け方としては64マスと17マス、6段と3段、6段と6段(中3段 が重なる)などがある • Rotated bitboardを使用しない場合は縦向きにした方が香車の 利きを求めやすくなる

Slide 72

Slide 72 text

まとめ • ボードゲームなどのAI開発には盤面処理の高速化が必要 • 高速化の手法の1つにビット配列がある • ビットボードは盤面をビット配列で管理することで高速に処理 する手法 • 魔法のアルゴリズムを使うと高速な計算ができる • CPU命令に魂を売り渡すとさらに速くことがある(裏切られる こともある)

Slide 73

Slide 73 text

参考文献 • セル・オートマトン | Wikipedia https://ja.wikipedia.org/wiki/セル・オートマトン • オセロをビットボードで実装する | Qiita https://qiita.com/sensuikan1973/items/459b3e11d91f3cb37e43 • ビットカウントする高速アルゴリズムをPythonで実装しながら 詳しく解説してみる | Qiita https://qiita.com/zawawahoge/items/8bbd4c2319e7f7746266 • やねうら王 http://yaneuraou.yaneu.com/