root/trunk/projects/bos/m2/allocation-cache.lisp

Revision 3895, 9.2 kB (checked in by ksprotte, 3 months ago)

bos big changes for allocation mechanics

  • ensure-m2 now uses (make-object 'm2 ...) instead of make-instance
  • make-contract has been restructured:
    • allocate-m2s-for-sale is called first - outside a transactional context
    • free m2s are then passed to (make-object 'contract ...)
  • allocation-area-free-m2s is now transient and computed lazily the
    first time when it is read
Line 
1 (in-package :bos.m2.allocation-cache)
2
3 ;;; just a utility for something else...
4 (defun allocation-area-save-to-file (area path)
5   (with-open-file (out path :direction :output :if-exists :supersede)
6     (multiple-value-bind (left top width height)
7         (bos.m2::compute-bounding-box (allocation-area-vertices area))
8       (loop for y from top upto (1- (+ top height))
9          do (loop for x from left upto (1- (+ left width))
10                for m2 = (get-m2 x y)
11                for ch = (cond
12                           ((null m2) #\space)
13                           ((m2-contract m2) #\x)
14                           (t #\.))
15                do (princ ch out))
16          do (terpri out)))))
17
18 (defun allocation-area2array (allocation-area)
19   "Returns a 2d array over the complete rectangular
20 area of ALLOCATION-AREA. Each spot contains NIL or a
21 m2 instance."
22   (with-accessors ((left allocation-area-left)
23                    (top allocation-area-top)
24                    (width allocation-area-width)
25                    (height allocation-area-height)
26                    (vertices allocation-area-vertices))
27       allocation-area
28     (let ((array (make-array (list width height))))
29       (loop for y from top upto (1- (+ top height))
30          do (loop for x from left upto (1- (+ left width))
31                for spot = (when (point-in-polygon-p x y vertices)
32                             (ensure-m2 x y))
33                for x0 = (- x left)
34                for y0 = (- y top)
35                do (setf (aref array x0 y0) spot)))
36       array)))
37
38 (defun in-array-bounds-p (array x y)
39   (and (<= 0 x) (< x (array-dimension array 0))
40        (<= 0 y) (< y (array-dimension array 1))))
41
42 (defun free-spot-p (array x y)
43   (let ((spot (aref array x y)))
44     (and spot (not (m2-contract spot)))))
45
46 (defmacro do-neighbour-coordinates (x y (x-var y-var) &body body)
47   (let ((=x= (gensym "X"))
48         (=y= (gensym "Y")))
49     `(let ((,=x= ,x)
50            (,=y= ,y)
51            (fn #'(lambda (,x-var ,y-var) ,@body)))
52        (funcall fn ,=x= (1+ ,=y=))
53        (funcall fn ,=x= (1- ,=y=))
54        (funcall fn (1+ ,=x=) ,=y=)
55        (funcall fn (1- ,=x=) ,=y=))))
56
57 (declaim (inline make-point-stack))
58 (defun make-point-stack (initial-x initial-y)
59   "A stack that can hold points of single X Y values."
60   (list initial-x initial-y))
61
62 (defmacro point-stack-pop (point-stack)
63   "Returns X and Y of next point."
64   `(values (pop ,point-stack) (pop ,point-stack)))
65
66 (defmacro point-stack-push (x y point-stack)
67   `(progn
68      (push ,y ,point-stack)
69      (push ,x ,point-stack)))
70
71 (defun extend-free-spot (array initial-x initial-y)
72   (iter
73     (with stack = (make-point-stack initial-x initial-y))
74     (for (values next-x next-y) = (point-stack-pop stack))
75     (while next-x)
76     (when (first-time-p)
77       (collect (aref array next-x next-y))
78       (setf (aref array next-x next-y) nil))
79     (do-neighbour-coordinates next-x next-y (x y)
80       (when (and (in-array-bounds-p array x y)
81                  (free-spot-p array x y))
82         (collect (aref array x y))
83         (setf (aref array x y) nil)
84         (point-stack-push x y stack)))))
85
86 (defun free-regions (allocation-area)
87   "Finds all free regions in ALLOCATION-AREA.
88 Each region is a list of m2 instances.
89 A list of those is returned."
90   (with-accessors ((left allocation-area-left)
91                    (top allocation-area-top)
92                    (width allocation-area-width)
93                    (height allocation-area-height))
94       allocation-area
95     (let ((array (allocation-area2array allocation-area)))
96       (iter
97         top
98         (for y below height)
99         (iter
100           (for x below width)
101           (unless (free-spot-p array x y)
102             (next-iteration))
103           (for region = (extend-free-spot array x y))
104           (in top (collect region)))))))
105
106 ;;; allocation-cache
107 (defvar *allocation-cache* nil)
108
109 (defconstant +threshold+ 200
110   "Free regions of size N where (<= 1 N +threshold+) are indexed.")
111
112 (defclass allocation-cache ()
113   ((index :reader allocation-cache-index :initform (make-array 200 :initial-element nil))
114    (ignored-size :accessor ignored-size :initform 0)
115    (hit-count :accessor hit-count :initform 0)
116    (miss-count :accessor miss-count :initform 0)))
117
118 (defun make-allocation-cache ()
119   (make-instance 'allocation-cache))
120
121 (defun clear-cache ()
122   (macrolet ((index ()
123                '(allocation-cache-index *allocation-cache*)))
124     (iter
125       (for i index-of-vector (index))
126       (setf (aref (index) i) nil))
127     (setf (ignored-size *allocation-cache*) 0)
128     *allocation-cache*))
129
130 (defstruct cache-entry
131   area region)
132
133 (defun cache-entry-valid-p (cache-entry)
134   (notany #'m2-contract (cache-entry-region cache-entry)))
135
136 (declaim (inline %index-lookup %index-pop index-lookup index-pop index-push size-indexed-p))
137 (defun %index-lookup (n)
138   "Will return the first index cache-entry of size N or
139 nil if it does not exist. The entry is not validated!"
140   (first (aref (allocation-cache-index *allocation-cache*) (1- n))))
141
142 (defun %index-pop (n)
143   "As INDEX-LOOKUP, but will remove the cache-entry
144 from the index. The entry is not validated!"
145   (pop (aref (allocation-cache-index *allocation-cache*) (1- n))))
146
147 (defun index-ensure-valid-entry-for-n (n)
148   "Ensures that the next available entry (the next
149 one that would be popped) is valid. If not, the entry
150 is removed recursively until a valid entry is available
151 or no entries for N are left."
152   (awhen (%index-lookup n)
153     (if (cache-entry-valid-p it)
154         it
155         (progn
156           (%index-pop n)
157           (index-ensure-valid-entry-for-n n)))))
158
159 (defun index-lookup (n)
160   "Will return the first valid cache-entry of size N or
161 nil if it does not exist."
162   (index-ensure-valid-entry-for-n n))
163
164 (defun index-pop (n)
165   "As INDEX-LOOKUP, but will remove the cache-entry
166 from the index."
167   (awhen (index-lookup n)
168     (%index-pop n)
169     it))
170
171 (defun index-push (n cache-entry)
172   "Add cache-entry (which has to be of size N) to index."
173   (push cache-entry (aref (allocation-cache-index *allocation-cache*) (1- n))))
174
175 (defun size-indexed-p (n)
176   "Are regions of size N indexed?"
177   (<= 1 n +threshold+))
178
179 (defun find-exact-match (n &key remove)
180   "Will return a free contiguous region of size N as a list of m2
181 instances and as a second value the corresponding allocation-area. If
182 no such region exactly matching N can be found, simply returns NIL.
183
184 If REMOVE is T then the returned region is removed from
185 the cache."
186   (flet ((hit (cache-entry)
187            (incf (hit-count *allocation-cache*))
188            (values (cache-entry-region cache-entry)
189                    (cache-entry-area cache-entry)))
190          (miss ()
191            (incf (miss-count *allocation-cache*))
192            nil))
193     (cond
194       ((not (size-indexed-p n)) (miss))
195       (remove (aif (index-pop n)
196                    (hit it)
197                    (miss)))
198       (t (aif (index-lookup n)
199               (hit it)
200               (miss))))))
201
202 (defun add-area (allocation-area)
203   (dolist (region (free-regions allocation-area)
204            allocation-area)
205     (let ((size (length region)))
206       (if (size-indexed-p size)
207           (index-push size (make-cache-entry :area allocation-area
208                                              :region region))
209           (incf (ignored-size *allocation-cache*) size)))))
210
211 (defun count-cache-entries ()
212   (iter
213     (for regions in-vector (allocation-cache-index *allocation-cache*))
214     (summing (length regions))))
215
216 (defun pprint-cache ()
217   (with-accessors ((hits hit-count)
218                    (misses miss-count))
219       *allocation-cache*
220     (let* ((total (+ (float (+ hits misses)) 0.001)) ; avoid getting 0 here
221            (hits-perc (round (* 100.0 (/ (float hits) total))))
222            (misses-perc (round (* 100.0 (/ (float misses) total)))))
223       (format t "cache hits:~15T~5D~25T~3D%~%" hits hits-perc)
224       (format t "cache misses:~15T~5D~25T~3D%~3%" misses misses-perc)
225       (format t "CACHE ENTRIES~2%")
226       (format t "number of m2 not in cache: ~A~2%" (ignored-size *allocation-cache*))
227       (format t "~5A~10T~A~%" "size" "count")
228       (format t "~5A~10T~A~%" "-----" "-----")
229       (iter
230         (for cache-entries in-vector (allocation-cache-index *allocation-cache*))
231         (for size upfrom 1)
232         (for count = (length cache-entries))
233         (unless (zerop count)
234           (format t "~5D~10T~5D~%" size count))))))
235
236 (defun rebuild-allocation-cache ()
237   (assert (or (in-transaction-p) (eql :snapshot (store-state *store*))) nil
238           "rebuild-allocation-cache may only be called in a transaction context")
239   (unless *allocation-cache*
240     (setq *allocation-cache* (make-allocation-cache)))
241   (clear-cache)
242   (dolist (allocation-area (class-instances 'allocation-area))
243     (when (allocation-area-active-p allocation-area)
244       (add-area allocation-area))))
245
246 (register-transient-init-function 'rebuild-allocation-cache)
247
248 (defun suggest-free-region-size ()
249   (iter
250     (for regions in-vector (allocation-cache-index *allocation-cache*))
251     (for size upfrom 1)
252     (for region-count = (length regions))
253     (unless (zerop region-count)
254       (leave size))))
255
256 (defmethod return-contract-m2s :after (m2s)
257   (when (<= (length m2s) +threshold+)
258     (let ((allocation-area (bos.m2::m2-allocation-area (first m2s))))
259       (index-push (length m2s) (make-cache-entry :area allocation-area
260                                                  :region m2s)))))
Note: See TracBrowser for help on using the browser.