It seems I have not chosen the best way to represent the bits of a bitboard. It seems that bitwise operations are only defined on integers, not on bitvectors, as I initially thought. There is a noticable speed difference between my implementation with bitvectors and a possible implementation with integers and bitwise operations. This can be shown as follows:
(use-modules (srfi srfi-19))
(define-syntax time
(syntax-rules ()
[(time expr expr* ...)
(begin
(define start-time (current-time time-monotonic))
expr
expr* ...
(define end-time (current-time time-monotonic))
(let* ([diff (time-difference end-time start-time)]
[elapsed-ns (+ (/ (time-nanosecond diff) 1e9)
(time-second diff))])
(display (format #t "~fs~%" elapsed-ns))))]))
(define (binary-bit-vector-operation bv1 bv2 proc)
(define (iter bit-list1 bit-list2)
(cond [(or (null? bit-list1) (null? bit-list2)) '()]
[else (cons (proc (car bit-list1)
(car bit-list2))
(iter (cdr bit-list1)
(cdr bit-list2)))]))
(list->bitvector
(iter (bitvector->list bv1)
(bitvector->list bv2))))
(time
(let ([bv1 #*10101010]
[bv2 #*01010101])
(do ([i 1 (+ i 1)])
([> i 1000000])
(binary-bit-vector-operation
bv1
bv2
(lambda (b1 b2)
(and b1 b2))))))
1.308778s #t
(use-modules (srfi srfi-19))
(define-syntax time
(syntax-rules ()
[(time expr expr* ...)
(begin
(define start-time (current-time time-monotonic))
expr
expr* ...
(define end-time (current-time time-monotonic))
(let* ([diff (time-difference end-time start-time)]
[elapsed-ns (+ (/ (time-nanosecond diff) 1e9)
(time-second diff))])
(display (format #t "~fs~%" elapsed-ns))))]))
(time
(let ([int1 #b10101010]
[int2 #b01010101])
(do ([i 1 (+ i 1)])
([> i 1000000])
(logand int1 int2))))
0.004594s #t
While my implementation using bitvectors is simpler for arbitrary procedures on bitboards, there might be a need to rewrite the bit board code to use integers in the future. Other bit board implementations seem to use integers to represent the bits, so maybe for most use cases one does not need more complex operations. Implementing more complex operations in Guile itself might again lead to a slowdown.
On such a rewrite, one consideration would be which bitwise operations library to use. There are the following options:
Here are some notes on equivalent operations for operations on bitvectors, when using integers to represent the bits of a bit board:
Here will ideas for an implementation using integers be collected.
I could simply use logical and operation to and the integer with 2^n where n is the number or id of the square on the chess board.
Simply check whether the number is greater than 0.
Is the number equal to 2^63 - 1?
Is the number equal to 0?
One can use logical and to get the value of single bits at the same position and then use logical operations on those to implement general procedures. The operation can still be an argument to make a higher order, more generic procedure. Instead of iterating through a bit vector, one can iterate through the bits of an integer this way.
Position | 2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
---|---|---|---|---|---|---|---|---|
Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Integer 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
Integer 2 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 0 |
Operation | AND | AND | AND | AND | AND | AND | AND | AND |
Result | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
Result value | 0*128 | 0*64 | 1*32 | 0*16 | 0*8 | 1*4 | 1*2 | 0*1 |
Then one can simply return the resulting integer, if the rest of the code has the proper abstractions.
Timings show, that the code using integers instead of bitvectors is slower for generic operations, but is faster for the primitive operations and, or and such:
(use-modules
;; SRFI 19: monotonic timing
(srfi srfi-19)
;; SRFI 60: procedures for treating integers as bits
(srfi srfi-60))
(define-syntax time
(syntax-rules ()
[(time expr expr* ...)
(begin
(define start-time (current-time time-monotonic))
expr
expr* ...
(define end-time (current-time time-monotonic))
(let* ([diff (time-difference end-time start-time)]
[elapsed-ns (+ (/ (time-nanosecond diff) 1e9)
(time-second diff))])
(display (format #f "~fs~%" elapsed-ns))))]))
(define-public val-of-bit-pos
(λ (bit-pos)
(expt 2 bit-pos)))
(define-public bit-mask-of-pos
(λ (pos)
(val-of-bit-pos pos)))
(define-public get-bit-value-at-pos
(λ (int pos)
"This procedure interprets 1 as true and 0 as false."
(if (= (bitwise-and int (bit-mask-of-pos pos)) 0)
0
1)))
(define-public generic-binary-bit-integer-operation
(λ (int1 int2 proc)
(let ([max-len (max (integer-length int1) (integer-length int2))])
(let iter ([pos 0] [result 0])
(cond
[(< pos max-len)
;; We can AND the integers with a bit mask, which only has a 1 at the position, which we
;; are interested in. If the bit of the integer at the position we are interested in is a
;; 1, then the AND operation will return an integer not equal 0. Otherwise, if the bit in
;; the integer is 0 instead, the AND operation will return 0 as well, because no other
;; bits are 1 in both the integer and the bit mask.
(let ([bit1 (get-bit-value-at-pos int1 pos)]
[bit2 (get-bit-value-at-pos int2 pos)])
(iter (+ pos 1)
(proc result pos bit1 bit2)))]
[else result])))))
(define example-reimplement-bitwise-and
(λ (prev-res pos bit1 bit2)
(+ prev-res
(* (if (= (+ bit1 bit2) 2)
1
0)
(val-of-bit-pos pos)))))
(time
(let ([int1 #b10101010]
[int2 #b01010101])
(do ([i 1 (+ i 1)])
([> i 1000000])
(generic-binary-bit-integer-operation
int1
int2
example-reimplement-bitwise-and))))
(time
(let ([int1 #b10101010]
[int2 #b01010101])
(do ([i 1 (+ i 1)])
([> i 1000000])
(logand int1 int2))))
4.375609s 0.004535s
Since usage of the generic procedure is probably not likely or only needed in few cases when dealing with bit board logic, I intend to implement the bit board logic in terms of integers.
... to be able to work offline.
create-bb
from bitboard-model
bitboard-operations
create-uniform-bb
from bitboard-model
There are still some problems with coverage:
Currently it seems that coverage is broken. It does not run code for accounting for procedure calls.