| 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))))) |
|---|