Common Lisp でビットボードを書きたいので、64 ビット整数が必要です。Common Lispで64ビット整数を取得するにはどうすればよいですか? また、すべてをゼロから作成せずにこれを達成するのに役立つライブラリはありますか?
4 に答える
(signed-byte 64)
変数をor型として宣言できます(unsigned-byte 64)
。
CL-USER> (typexpand '(unsigned-byte 64))
(INTEGER 0 18446744073709551615)
T
CL-USER> (typexpand '(signed-byte 64))
(INTEGER -9223372036854775808 9223372036854775807)
T
実際にこれを 8 連続バイトに詰め込むのに十分賢いかどうか、またはこれに bignum を使用するかどうかは、実装に依存します。適切なoptimize
宣言が役立つ場合があります。
これは、そのような型宣言の (非常に単純な) 例であり、整数をバイナリで処理します。
(let* ((x #b01)
(y #b10)
(z (logior x y)))
(declare ((signed-byte 64) x y z))
(format t "~a~%" (logbitp 1 x))
(format t "~a~%" (logbitp 1 (logior x (ash 1 1))))
(format t "~b~%" z))
Output:
NIL
T
11
以下は、整数のビットに対する単純なセッターと、対応するゲッターを取得するための setf-expander の定義です。
(define-setf-expander logbit (index place &environment env)
(multiple-value-bind (temps vals stores store-form access-form)
(get-setf-expansion place env)
(let ((i (gensym))
(store (gensym))
(stemp (first stores)))
(values `(,i ,@temps)
`(,index ,@vals)
`(,store)
`(let ((,stemp (dpb ,store (byte 1 ,i) ,access-form))
,@(cdr stores))
,store-form
,store)
`(logbit ,i ,access-form)))))
(defun logbit (index integer)
(ldb (byte 1 index) integer))
これらは次のように使用できます。
(let ((x 1))
(setf (logbit 3 x) 1)
x)
==> 9
(let ((x 9))
(setf (logbit 3 x) 0)
x)
==> 1
(logbit 3 1)
==> 0
(logbit 3 9)
==> 1
ポータブルCommonLispでは、「整数」は好きなだけ大きくなります。'fixnums'と呼ばれる整数のより効率的なサブセットがあります。fixnumの正確な範囲は、実装によって異なります。しかし、ほとんどのCommon Lisp実装は型タグビットを必要とするため、通常、使用できるのは完全な64ビット(64ビットアーキテクチャ上)ではありません。ユーザーにとっては、大きな違いはありません。Fixnumは整数のサブセットであり、2つのfixnumを追加して、非fixnum整数の結果を得ることができます。観察できる唯一の違いは、非fixnum整数を使用した計算は遅く、より多くのストレージが必要なことです...一般に、整数を使用して計算を行う場合は、64ビットで計算することを宣言する必要はありません。 。整数とそれらの通常の演算を使用するだけです。
実際の64ビットの大きな整数(64ビットのみで表され、タグなどは含まれない)とそれらを使用した計算が必要な場合は、ポータブルANSICL機能をそのままにしておきます。CLISPがそれをサポートするかどうか、またどのようにサポートするかは、CLISPメーリングリストで尋ねるのが最適です。
ドキュメンテーション
8x8ビットボードを実装するためのビットベクトル/配列の使用例(厳密なアセンブラーコードを取得する方法を示すために、残酷かつ時期尚早に最適化されたコードから開始):
(defun make-bitboard ()
(make-array '(8 8) :element-type '(mod 2) :initial-element 0))
MAKE-BITBOARD
ビットの配列として8x8ビットボードを作成します。SBCLを使用する場合、これは内部的に要素ごとに1ビットとして表されます(したがって、64ビット+配列インスタンスのオーバーヘッドがあります)。ボードにアクセスするときに最適化を要求すると、高速なコードが得られます。
(declaim (inline get-bitboard))
(defun get-bitboard (bit-board x y)
(declare (optimize speed (safety 0) (debug 0))
(type (simple-array (mod 2) (8 8)) bit-board)
(type fixnum x y))
(aref bit-board x y))
(declaim (notinline get-bitboard))
は、DECLAIM
のローカル
インライン化要求を
許可するためにありGET-BITBOARD
ます。
使用例GET-BITBOARD
:
(defun use-bitboard (bit-board)
(declare (optimize speed (safety 0) (debug 0))
(type (simple-array (mod 2) (8 8)) bit-board)
(inline get-bitboard))
(let ((sum 0))
(declare (type fixnum sum))
(dotimes (i 8)
(declare (type fixnum i))
(dotimes (j 8)
(declare (type fixnum j))
(incf sum (the (mod 2) (get-bitboard bit-board i j)))))
sum))
SET-BITBOARD
まだないので、使用例USE-BITBOARD
は次のとおりです。
(use-bitboard (make-bitboard))
逆アセンブルUSE-BITBOARD
(SBCL、Linux x64)は、コンパイラーがインライン化されていることを示していますGET-BITBOARD
。
; disassembly for USE-BITBOARD
; 030F96A2: 31F6 XOR ESI, ESI ; no-arg-parsing entry point
; 6A4: 31D2 XOR EDX, EDX
; 6A6: EB54 JMP L3
; 6A8: 90 NOP
; 6A9: 90 NOP
; 6AA: 90 NOP
; 6AB: 90 NOP
; 6AC: 90 NOP
; 6AD: 90 NOP
; 6AE: 90 NOP
; 6AF: 90 NOP
; 6B0: L0: 31DB XOR EBX, EBX
; 6B2: EB3E JMP L2
; 6B4: 90 NOP
; 6B5: 90 NOP
; 6B6: 90 NOP
; 6B7: 90 NOP
; 6B8: 90 NOP
; 6B9: 90 NOP
; 6BA: 90 NOP
; 6BB: 90 NOP
; 6BC: 90 NOP
; 6BD: 90 NOP
; 6BE: 90 NOP
; 6BF: 90 NOP
; 6C0: L1: 488D04D500000000 LEA RAX, [RDX*8]
; 6C8: 4801D8 ADD RAX, RBX
; 6CB: 4C8B4711 MOV R8, [RDI+17]
; 6CF: 48D1F8 SAR RAX, 1
; 6D2: 488BC8 MOV RCX, RAX
; 6D5: 48C1E906 SHR RCX, 6
; 6D9: 4D8B44C801 MOV R8, [R8+RCX*8+1]
; 6DE: 488BC8 MOV RCX, RAX
; 6E1: 49D3E8 SHR R8, CL
; 6E4: 4983E001 AND R8, 1
; 6E8: 49D1E0 SHL R8, 1
; 6EB: 4C01C6 ADD RSI, R8
; 6EE: 4883C302 ADD RBX, 2
; 6F2: L2: 4883FB10 CMP RBX, 16
; 6F6: 7CC8 JL L1
; 6F8: 4883C202 ADD RDX, 2
; 6FC: L3: 4883FA10 CMP RDX, 16
; 700: 7CAE JL L0
; 702: 488BD6 MOV RDX, RSI
; 705: 488BE5 MOV RSP, RBP
; 708: F8 CLC
; 709: 5D POP RBP
; 70A: C3 RET
コンパイラがこれらすべてを配置した理由はわかりませんがNOP
(後でインストルメンテーション用のスペースを残しますか?アライメントですか?)、最後のコードを見ると、かなりコンパクトです(もちろん、手作りのアセンブラほどコンパクトではありません)。
これは、時期尚早の最適化の明らかなケースです。ここから始める正しい方法は、単に次のように書くことです。
(defun get-bitboard (bit-board x y)
(aref bit-board x y))
(defun use-bitboard (bit-board)
(let ((sum 0))
(dotimes (i 8)
(dotimes (j 8)
(incf sum (get-bitboard bit-board i j))))
sum))
...次に、ビットボードを使用するゲームコードを実行するときにプロファイラーを使用して、CPUのボトルネックがどこにあるかを確認します。SBCLには、優れた 統計プロファイラーが含まれています。
速度を宣言せずに、より単純で低速なコードから始めるのが最善です。コードのサイズを比較するだけです-私はたくさんの宣言を含むコードから始めて、最後の単純なコードを比較してさらに単純に見えるようにしました:-)。ここでの利点は、アイデアを試すときにCommon Lispをスクリプト/プロトタイピング言語として扱い、プロファイラーが提案するコードからより多くのパフォーマンスを引き出すことができることです。
アセンブリコードは、ボード全体を1つの64ビットレジスタにロードしてから個々のビットにアクセスするほど厳密ではないことは明らかです。ただし、1平方あたり1ビット以上が必要であると突然判断した場合は、アセンブラコードを変更するよりも、CLコードを変更する方がはるかに簡単です(たとえば、配列タイプをから'(mod 2)
に変更するだけです)。'(mod 16)
64ビット整数のようなものではなく、任意のサイズのビット配列であるビットベクトルを使用したい。実装は、内部表現を処理します。