уторак, 27. октобар 2009.

Решавање судоку

Прошле године су нам у посети били моји родитељи и при том сам сазнао да је мој отац постао пасионирани играч судоку. Мало сам играо и ја, врло интересантна мозгалица. Природно, дошао сам на идеју да решавање "аутоматизујем". У то време сам учио "Scheme" језик - шема ме још занима али се нема времена.

Ипак, склепао сам некакав програмчић а ових дана га пречистио и проверио. Програм следи у целини.

Програм је развијан и тестиран коришћењем "Petite Chez Scheme" http://www.scheme.com/petitechezscheme.html.


; Решавање судоку матрице.
; Функција за решавање је:
; (solve sudo azbuka)
; где је sudo низ који описује почетно
; стање судоку, а azbuka низ који дефинише
; могуће елементе. Класичан судоку ће
; у азбуци имати цифре 1 до 9.


;
; Дефиниција примера судоку проблема
; Судоку таблица се задаје као низ
; по редовима.
;
; Редови и колоне су нумерисане
; бројевима 1 до 9.
;
; Непознати елементи су задати елементом
; који није садржан у азбуци. У примеру
; који следи, елементе које сам хтео да
; прогласим непознатим сам обележио подвлаком.
;
; Резултат обележава решене елементе
; заградом (под листа). Уколико
; једнозначно решење није нађено,
; у загради ће бити изслитана могућа
; решења.
;
(define sudo '(  _1  2 _3  4  5  6 _7 _8 _9
                  4 _5  6  7 _8 _9  1  2  3
                 _7 _8 _9 _1  2  3 _4 _5  6
                  2  3 _4  5  6  7 _8 _9  1
                 _5  6  7 _8 _9  1  2  3  4
                 _8 _9  1  2  3  4  5  6  7
                 _3  4  5  6 _7 _8 _9  1  2
                 _6  7 _8 _9  1  2  3  4  5
                 _9 _1  2  3 _4  5  6 _7  8   ))


;
; Азбука.
; Азбука може бити произвољна све док задовољава
; услов да је број елемената једнак квадрату
; целобројног позитивног броја.
;       
(define azbuka '(1 2 3 4 5 6 7 8 9))

(define a2 '(0 1 2 3))
(define s2 '( ? 1 2 3
              3 2 1 ?
              1 3 0 2
              2 0 ? 1))
              


;
; Основна функција за решавање судоку.
;
(define (solve sudo azbuka)
    (solve1 sudo azbuka 0))

; 
; Помоћна функција за решавање
;
(define (solve1 sudo azbuka rc)
    (cond
   ((>= rc (* (list-len azbuka) (list-len azbuka))) sudo)
      (else
        (solve1
          (append 
            (front (reduce sudo azbuka) rc)
            (cons
              (possible (reduce sudo azbuka) azbuka (row-idx azbuka rc) (col-idx azbuka rc))
              (skip-n (reduce sudo azbuka) (1+ rc))))
          azbuka
          (1+ rc)))))



;
; Редукуј све непознате чланове на листе могућих
; решења
;
(define (reduce sudo azbuka)
 (reduce1 sudo azbuka 0))

;
; Помоћна функција.
; Редукуј све непознате чланове на листе могућих
;
(define (reduce1 sudo azbuka rc)
 (cond
  ((> (1+ rc) (* (list-len azbuka) (list-len azbuka))) '())
  (else
   (cons
    (possible sudo azbuka (row-idx azbuka rc) (col-idx azbuka rc))
    (reduce1 sudo azbuka (1+ rc))))))


;
; Помоћна функција - број елемената у 
; датој листи.
;
(define (list-len lst)
    (cond
      ((or (null? lst) (is-atom? lst)) 0)
      (else
        (1+ (list-len (cdr lst))))))
        
; 
; Помоћна функција - израчунава димензију под-судоку
; матрице.
;
(define (koren azbuka) (sqrt (list-len azbuka)))
                                                                     
;                                                                    
; Одређивање природе вредности aorl
; Враћа #f уколико дата вредност није
; атом.
;
(define is-atom? atom?)

;(define (is-atom? aorl)
;    (cond
;      ((or 
;           (list? aorl)
;           (not (number? aorl))) #f)
;      (else (atom? aorl))))

; 
; Да ли је дати елемент слово азбуке
;
(define (member? at lst)
 (cond
  ((null? lst) #f)
  (else
   (or (eq? at (car lst))
    (member? at (cdr lst))))))

;
; Да ли је дата вредност задата?
;
(define (is-given? aorl azbuka)
    (and (is-atom? aorl) (member? aorl azbuka)))

;
; Да ли је вредност решена?
;
; Решена је само онда када је представљена
; листом са тачно једним елементом који
; није 0.
;
(define (is-solved? lat azbuka)
    (cond
      ((eq? 1 (list-len lat)) #t)
      (else (is-given? lat azbuka))))

;
; Помоћна функција - број елемената у 
; датој листи.
;
(define (list-len lst)
    (cond
      ((or (null? lst) (is-atom? lst)) 0)
      (else
        (1+ (list-len (cdr lst))))))

;
; Прескочи n елемената и врати
; n-ти елемент.
;
(define (skip-n sudo n)
    (cond
      ((null? sudo) '())
      ((eq? 0 n) sudo)
      (else
        (skip-n (cdr sudo) (1- n)))))

;
; Врати листу од n елемената почев од
; датог елемента sudo
;
(define (front sudo n)
    (cond
      ((null? sudo) '())
      ((eq? 0 n) '())
      (else
        (cons (car sudo) (front (cdr sudo) (1- n))))))

;
; Издвој ред n
; 
(define (row sudo azbuka n)
    (cond
      ((null? sudo) '())
      (else
        (front (skip-n sudo (* (1- n) (list-len azbuka))) (list-len azbuka)))))

;
; Помоћна функција за издвајање колоне
;
(define (getcol sudo azbuka n)
    (cond
      ((null? sudo) '())
      ((eq? 0 n) '())
      (else
        (cons (car sudo) (getcol (skip-n sudo (list-len azbuka)) azbuka (1- n))))))

;
; Издвоји колону n
;
(define (col sudo azbuka n)
    (cond
      ((null? sudo) '())
      (else
        (getcol (skip-n sudo (1- n)) azbuka (list-len azbuka)))))
       
;
; помоћна функција - врати ред (почев од 1)
; на основу датог индекса листе која дефинише
; судоку. Индекс почиње са 0.
; 
(define (row-idx azbuka idx)
 (1+ (floor (/ idx (list-len azbuka)))))
 
;
; помоћна функција - врати колону (почев од 1)
; на основу датог индекса листе која дефинише
; судоку. Индекс почиње са 0.
; 
(define (col-idx azbuka idx)
 (1+ (- idx (* (1- (row-idx azbuka idx)) (list-len azbuka)))))

;
; Помоћна функција за издвајање "мини судоку" односно
; подматрице
;
(define (sub-sudo-row sudo azbuka r c rn koren)
 (cond 
  ((> rn koren) '())
  ((null? sudo) '())
 (else
  (append
   (front (skip-n (row sudo azbuka (+ rn (* (1- r) koren))) (* (1- c) koren)) koren)
   (sub-sudo-row sudo azbuka r c (1+ rn) koren)))))

;
; Издвој мини судоку (a пута a, где је а корен
; броја елемената у азбуци) са задате позиције.
; Позиције мини судоку су нумерисане 1 до (list-len azbuka).
;
(define (sub-sudo sudo azbuka r c)
 (sub-sudo-row sudo azbuka r c 1 (koren azbuka)))

;
; 
;
(define (extract-solved su azbuka)
    (cond
      ((null? su) '())
      ((is-solved? (car su) azbuka) 
    (cons 
  (cond
    ((list? (car su)) (caar su))
    (else
    (car su)))
    (extract-solved (cdr su) azbuka)))
      (else
        (extract-solved (cdr su) azbuka))))

;
; Разлика две судоку
;
(define (diff su lat)
    (cond
      ((null? su) '())
      ((null? lat) su)
      ((member? (car su) lat) (diff (cdr su) lat))
      (else
        (cons (car su) (diff (cdr su) lat)))))

;
; Да ли је вредност at члан низа lat?
;
(define (member? at lat)
    (cond
      ((null? lat) #f)
      ((eq? at (car lat)) #t)
      (else
        (member? at (cdr lat)))))

;
; Врати сва немогућа решења за дату позицију.
; (сви елементи азбуке који су већ дати или
; нађени у датом реду, колони и под судоку).
;
(define (allsolved sudo azbuka r c)
    (append
      (extract-solved (row sudo azbuka r) azbuka)
      (extract-solved (col sudo azbuka c) azbuka)
      (extract-solved 
  (sub-sudo 
   sudo azbuka (ceiling (/ r (koren azbuka))) (ceiling (/ c (koren azbuka))))
  azbuka)))


;
; Врати листу могућих решења за дати елемент.
; 
(define (possible sudo azbuka r c)
    (cond
      ((is-solved? (element sudo azbuka r c) azbuka)
       (element sudo azbuka r c))
      (else
        (diff azbuka (allsolved sudo azbuka r c)))))

;
; Помоћна функција. Врати дати елемент.
; Ред и колона почињу од 1
;
(define (element sudo azbuka r c)
    (car (skip-n sudo (+ (* (list-len azbuka) (1- r)) (1- c)))))


gcc и груписање функција

У неким ретким случајевима, потребно је оптимизовати програм тако да се смањи растојање између функција које се често позивају. Могући добитак од овакве оптимизације је смањење "TLB misses" односно потребе да процесор учитава податке за превођење виртуелне у физичку адресу за дати блок. Друга могућа добит је искоришћење различитих нивоа кеширања: уколико су функције физички близу, велика је вероватноћа да ће, док се прва извршава, друга бити учитана у кеш.

Конкретан пример који имам је оптимизација кода за "лењо разрешавање" адреса симбола у динамички повезаним програмима (lazy binding).

Наш оперативни систем (QNX Neutrino) има динамичко повезивање, али не "уме" да то уради "лењо" односно на захтев. Како сада ради, наш динамички повезивач разрешава све адресе симбола на самом почетку, пре него што програм стигне у "main". Последњих неколико дана сам радио на имплементацији која већ постоји на већини јуникс система, а која подразумева разрешавање симбола функција у тренутку првог позива.

Како између два разрешења може доста кода да се изврши, велика је вероватноћа да ће функције потребне за лењо разрешење већ бити избачене из кеша, а такође из процесорске табеле за превођење адреса (TLB) избачене ставке које се односе на физичке странице меморије у којима су те функције. Груписањем функција тако да буду близу једна другој, идеално у једној физичкој страници меморије, ће смањити број промашаја.

Приликом прве компилације установио сам да су функције на приличном растојању једна од друге. Конкретно, приликом дисасемблирања објектног фајла, добио сам ово:

bind_func     0x000000cc
 resolve_func  0x000029e0
 resolve_rels  0x00001010
 lookup_global 0x00000c68

Види се да би фунције биле распоређене у бар 3 меморијске странице (под условом да је страница 4KiB).

Да бих груписао исте, искористио сам атрибут фунције "hot" уведен са gcc верзијом 4.3. Горе наведеним функцијама сам у декларацију додао: __attribute__((hot)). Gcc тако означене функције оптимизује агресивније али и групише у посебну секцију ".text.hot". На крају процеса повезивања, функције су распоређене једна до друге:

bind_func    0x0003f3f8
 resolve_rels 0x0003f418
 resolve_func 0x0003f88c

Интересантно је да је lookup_global "нестала" односно апсорбована у функције које је позивају.

У сваком случају, уместо 4 физичке странице сада функције могу да стану у само једну и распоређене су једна уз другу чиме се повећава шанса да у тренутку позива већ буду у кешу.


Тек треба да упоредим да ли се овиме добија нешто што може да се измери. Вероватно ће доста зависити од процесора па и самог уређаја.

субота, 24. октобар 2009.

7-zip

Коначно сам инсталирао 7-zip (http://www.7-zip.org/) и код куће. Већ неколико месеци га користим на послу. 7-zip "разуме" различите формате архива, између осталих .zip, .rar, .bz2...

WinZip ми стварно неће недостајати.