Пример использования битовых векторов / массивов для реализации битовой платы 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
s, чтобы разрешить местный
запросов на встраивание для
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))
... и затем использовать профилировщик при запуске игрового кода, который использует
плата, чтобы увидеть узкие места процессора. SBCL включает в себя хороший
статистический профилировщик .
Начиная с более простого и медленного кода, без объявлений для
скорость, лучше. Просто сравните размер кода - я начал с
код с множеством объявлений, чтобы сделать простой код в конце
Выглядит еще проще в сравнении :-). Преимущество здесь в том, что вы
может рассматривать Common Lisp как язык сценариев и прототипов при попытке
идеи, а затем выжать больше производительности из кода, что
Профилировщик предлагает.
Код сборки явно не такой жесткий, как загрузка всей платы в
один 64-битный регистр, а затем доступ к отдельным битам. Но если ты
вдруг решить, что вы хотите больше, чем 1 бит на квадрат, это очень
проще изменить код CL, чем изменить код ассемблера (просто
везде изменить тип массива с '(mod 2)
на '(mod 16)
, для
экземпляр).