common lisp最適化発展

21
Common Lisp最適化発展 TOYOZUMI Kouichi @TOYOZUMIKouichi FILMASSEMBLER Copyright 2014 TOYOZUMI Kouichi and FILMASSEMBLER all rights reserved.

Upload: toyozumi-kouichi

Post on 14-Jun-2015

799 views

Category:

Engineering


4 download

TRANSCRIPT

Page 1: Common lisp最適化発展

Common Lisp最適化発展TOYOZUMI Kouichi

@TOYOZUMIKouichi

FILMASSEMBLER

Copyright 2014 TOYOZUMI Kouichi and FILMASSEMBLER all rights reserved.

Page 2: Common lisp最適化発展

FILMASSEMBLER 2

今日話すこと

1. 空間フィルタとその難しさ2. 攻略の手順3. 最適化の実装

・コード例はあくまで例です ・実際に動かしてません ・以前実装して試したことはあります・かなりわかりづらいです ・詳細はMayakaのソースコードを見てください・ベンチマークは大変なので、いつかまとめてな感じで ・(ヘタなこと言うと後々難しいので) ・次々回予定してます

はじめに

Page 3: Common lisp最適化発展

FILMASSEMBLER 3

輪郭抽出とかできます

・いままでのMAP-IMAGEがやってきたのとは違います ・今までのは「画素をとって画素を返す」 ・今度のはまわりの画素も使います  ・ので、「空間」です・例えばこんなことができます ・輪郭抽出 ・ブラしたりボカしたり

空間フィルタとその難しさ

Page 4: Common lisp最適化発展

FILMASSEMBLER 4

空間フィルタの内実

・輪郭抽出系のフィルタ ・「今の画素」の周囲の画素成分を削り「今の画素」に入れる ・すると輪郭しか残らない ・「微分する」とかいうらしい・ぼかし系のフィルタ ・「今の画素」とその周囲の画素成分から  平均値や中央値を取り出し、「今の画素」に入れる ・すると輪郭が潰れる

空間フィルタとその難しさ

Page 5: Common lisp最適化発展

FILMASSEMBLER 5

空間フィルタの多様性を形作るもの

・「空間」の大きさ ・大抵3x3か5x5・単なる係数適用か、それ以外か ・輪郭抽出は係数適用 ・ボカしはそれ以外

空間フィルタとその難しさ

Page 6: Common lisp最適化発展

FILMASSEMBLER 6

空間フィルタが難しくなる理由

・空間の大きさが可変 ・決め打ちでコードを書けない ・空間を走査する̶̶つまり条件分岐が必要になる・「今の画素」の周囲を利用する ・「そこに画素はあるのか?」  ・当然条件分岐  ・あったら使う、なかったらどうする?・関数の引数が三十六個あるよ!がんばってね! ・配列にまとめましょうか  ・じゃじゃーん、メモリ確保でーす! ・関数の引数が百個あるよ!がんばってね!・速度的にも記述的にも難しいです

空間フィルタとその難しさ

Page 7: Common lisp最適化発展

FILMASSEMBLER 7

書きやすいやつからやります

・単なる係数適用のフィルタ ・もう係数だけ渡せば良いじゃない ・簡潔になるし、最適化しやすくなる・そうでないフィルタ ・できるだけがんばりましょう

本日は単なる係数適用のフィルタである、map-image1-kernelについてやります

攻略の手順

Page 8: Common lisp最適化発展

FILMASSEMBLER 8

係数を適用する空間フィルタ攻略の手順

0 1 0

1 -4 1

0 1 0

-1 -1 -1

-1 8 -1

-1 -1 -1

Page 9: Common lisp最適化発展

FILMASSEMBLER 9

係数を適用する空間フィルタ攻略の手順

(map-image1-kernel image ‘((0 1 0) (1 -4 1) (0 1 0)))

(map-image1-kernel image ‘((-1 -1 -1) (-1 8 -1) (-1 -1 -1)))

Page 10: Common lisp最適化発展

FILMASSEMBLER 10

係数を適用する空間フィルタ攻略の手順

-1 -3 -4 -3 -1-3 0 6 0 -3-4 6 20 6 -4-3 0 6 0 -3-1 -3 -4 -3 -1

Page 11: Common lisp最適化発展

FILMASSEMBLER 11

係数を適用する空間フィルタの特徴

・ユーザが自由記述するコードがない ・許容を考える必要がない  ・つまり、限界まで最適化できる  ・引数が9個や36個の関数オブジェクトについて考えなくて良い・窓(対象の画素とその周囲)の大きさは可変のまま ・しかしやることはかけ算と足し算  ・順番にやっていけば話は済む・だから、どれだけ「順番にやる」を最適化できるか ・単純に最適なコード ・「画素の有無」 ・「可変の窓」

攻略の手順

Page 12: Common lisp最適化発展

FILMASSEMBLER 12

単純に最適なコードを作る

・係数の適用 ・対象の画素と周囲の画素の各成分値に特定の値をかける ・それを足しこむ・画像と「対象の画素」の位置から それを行う最適化されたコードを作ってしまう

攻略の手順

Page 13: Common lisp最適化発展

FILMASSEMBLER 13

「画素の有無」問題の解決

・「画素の有無」問題 ・画像の端を処理するとき、「周囲の画素」が存在しない  ・右上に来たら右と上に画素はなくなる  ・左に来たら左に画素はなくなる ・画素があるかないか問い合わせると条件分岐  ・遅くなる・最適化の基本 ・「反復の外でやればいいのに反復の中でやった」  ・この問題は「反復の外」で解決可能・3x3の窓なら、「周囲の画素が存在しない」のは8パターン ・上、右上、左上、下、右下、左下、右、左 ・全部で処理のパターンは9

攻略の手順

Page 14: Common lisp最適化発展

・赤マスの画素の取得を試みず、全体の係数の和が0になるよう 調整したコードを生成する

FILMASSEMBLER 14

「画素の有無」問題の解決攻略の手順

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

1 -4 1

0 1 0

Page 15: Common lisp最適化発展

・係数0のマスはすべてにおいて取得する必要すらないため、 コードをカットして高速化を図る

FILMASSEMBLER 15

「画素の有無」問題の解決攻略の手順

0 0 0

1 -2 0

0 1 0

0 0 0

1 -3 1

0 1 0

0 0 0

0 -2 1

0 1 0

0 1 0

1 -3 0

0 1 0

0 1 0

1 -4 1

0 1 0

0 1 0

0 -3 1

0 1 0

0 1 0

1 -2 0

0 0 0

0 1 0

1 -3 1

0 0 0

0 1 0

0 -2 1

0 0 0

Page 16: Common lisp最適化発展

FILMASSEMBLER 16

「可変の窓」問題の解決

・先に9つのパターンを作ってしまう(部分評価) ・「単純に最適化されたコード」が9つ出現 ・これを適した場所で実行する  ・条件分岐は残るが数はかなり減る・別にこれが3x3であろうと5x5だろうと7x7でも関係ない ・ただ「単純に最適化されたコード」の種類が増えるだけ・実行時コード生成でほとんどの条件分岐を反復外に追い出せる・コンパイラマクロを使えばコード生成をコンパイル時に行える ・普通map-image1-kernelに渡すリストはリテラル  ・コンパイル時にコード生成可能 ・リテラルでない場合は実行時コード生成

攻略の手順

Page 17: Common lisp最適化発展

FILMASSEMBLER 17

map-image1-kernel

・インターフェイスとなる関数と、コンパイラマクロの組み合わせ・係数が記されたリストがリテラルで与えられたら埋め込む ・そうでない場合は同じ処理を実行時コード生成

最適化の実装

(defun map-image1-kernel (source kernel &key (apply-1st t) (apply-2nd t) (apply-3rd t) (apply-4th t)) "SOURCEに対してKERNELによる空間フィルタを適用し、結果を画像として返します。" (bind-kernel-half-width-and-height kernel (window-half-width window-half-height) (map-image1-kernel-core source window-half-width window-half-height (window-coefficient-appliers (image-width source) kernel apply-1st apply-2nd apply-3rd apply-4th) )))

(define-compiler-macro map-image1-kernel (&whole form source kernel &key (apply-1st t) (apply-2nd t) (apply-3rd t) (apply-4th t)) (if (ignore-errors (listp (eval kernel))) (let1 coefficient-applier (window-coefficient-appliers 'width (eval kernel) apply-1st apply-2nd apply-3rd apply-4th) (bind-kernel-half-width-and-height (eval kernel) (window-half-width window-half-height) `(map-image1-kernel-core ,source ,window-half-width ,window-half-height ',coefficient-applier))) `,form))

Page 18: Common lisp最適化発展

FILMASSEMBLER 18

map-image1-kernel‒core

・map-image1-kernelでコンパイラマクロを短く書くためのもの ・共通部分を追い出しているだけ・基本は空間フィルタで共通のdopixels-windowの呼び出し ・つまりこれも大したことやってない

最適化の実装

(defunsafe map-image1-kernel-core image ((image source) (fixnum window-half-width window-half-height) (list coefficient-appliers)) (bind-width-and-height source (width height) (bind-destination-and-each-pixels source (destination src-pixels dst-pixels) (dopixels-window width height window-half-width window-half-height coefficient-appliers (x y coefficient-applier) (funcall coefficient-applier (offset x y width) src-pixels dst-pixels width)) destination)))

Page 19: Common lisp最適化発展

FILMASSEMBLER 19

dopixels-window

・dopixel-window-codeなる関数の返り値をevalする ・functions-with-careから適した関数がfunctionに束縛される ・これを使ってコードを書けばいい・dopixel-window-code ・こいつがコードを作る

最適化の実装

(defmacro dopixels-window (width height window-half-width window-half-height functions-with-care (x y function &optional name) &body body) (with-gensyms (main) `(typed-let1 function ,main (typed-lambda ((fixnum ,x,y) (function ,function)) ,@body) (eval (dopixels-window-code ,width ,height ,window-half-width ,window-half-height ,functions-with-care ,name ,main)))))

(defun dopixels-window-code (width height window-half-width window-half-height functions-with-care name main) "" (with-gensyms (xx yy) (let1 window-width (1+ (* window-half-width 2)) `(block ,name ;; top line ,@(loop for ay fixnum from 0 below window-half-height append (append ;; top left (loop for ax fixnum from 0 below window-half-width collect `(funcall ,main ,ax ,ay (nth ,(+ ax (* ay window-width)) ',functions-with-care))) ;; top mid (list `(loop for ,xx fixnum from ,window-half-width below (- ,width ,window-half-width) do (funcall ,main ,xx ,ay (nth ,(+ window-half-width (* ay window-width)) ',functions-with-care)))) ;; top right (loop for ax fixnum from window-half-width downto 1 collect `(funcall ,main (- ,width ,ax) ,ay (nth ,(+ (- window-width ax) (* ay window-width)) ',functions-with-care))) )) ;; mid (loop for ,yy fixnum from ,window-half-height below (- ,height ',window-half-height) do ;; left ,@(loop for ax fixnum from 0 below window-half-width collect `(funcall ,main ,ax ,yy (nth ,(+ (* window-half-height window-width) ax) ',functions-with-care))) ;; mid (loop for ,xx fixnum from ,window-half-width below (- ,width ,window-half-width) do (funcall ,main ,xx ,yy (nth ,(+ (* window-half-height window-width) window-half-width) ',functions-with-care))) ;; right ,@(loop for ax fixnum from window-half-width downto 1 collect `(funcall ,main (- ,width ,ax) ,yy (nth ,(+ (* window-half-height window-width) (- window-width ax)) ',functions-with-care)))) ;; bottom line ,@(loop for ay fixnum from window-half-height downto 1 append (append ;; bottom left (loop for ax fixnum from 0 below window-half-width collect `(funcall ,main ,ax (- ,height ,ay) (nth ,(+ (* window-width (1+ window-half-height)) (* window-width (- window-half-height ay)) ax) ',functions-with-care))) ;; bottom mid (list `(loop for ,xx fixnum from ,window-half-width below (- ,width ,window-half-width) do (funcall ,main ,xx (- ,height ,ay) (nth ,(+ (* window-width (1+ window-half-height)) (* window-width (- window-half-height ay)) window-half-width) ',functions-with-care)))) ;; bottom right (loop for ax fixnum from window-half-width downto 1 collect `(funcall ,main (- ,width ,ax) (- ,height ,ay) (nth ,(+ (* window-width (1+ window-half-height)) (* window-width (- window-half-height ay)) (- window-width ax)) ',functions-with-care)))) )))))

←詳しくはソースコードを見てね!

Page 20: Common lisp最適化発展

FILMASSEMBLER 20

window-coefficient-appliers・「現在の画素」と「存在する周辺の画素」から 係数を適用して新しい画素を返す関数群 ・dopixels-window内ではfunctions-with-careとなる・lazy-evalなので実行時にevalされる ・コンパイラマクロではコンパイル時

最適化の実装

(define-lazy-evaluation window-coefficient-appliers (width kernel apply-1st apply-2nd apply-3rd apply-4th) (with-gensyms (offset src-pixels dst-pixels 1st 2nd 3rd 4th tmp-1st tmp-2nd tmp-3rd tmp-4th w) (let1 ignores (remove nil (list (if (not apply-1st) tmp-1st) (if (not apply-2nd) tmp-2nd) (if (not apply-3rd) tmp-3rd) (if (not apply-4th) tmp-4th))) `(list ,@(loop for corrects-coeffients-and-removed in (window-corrects-coeffients-and-removed (if (numberp width) width w) kernel) collect (destructuring-bind (corrects coefficients removes) corrects-coeffients-and-removed `(typed-lambda ((fixnum ,offset) (pixels ,src-pixels ,dst-pixels) (fixnum ,w)) ,(if (numberp width) `(declare (ignore ,w))) (typed-let (((component-t ,1st) ,(if apply-1st (if removes `(* ,(apply #'+ removes) (pixels-1st-component ,src-pixels ,offset)) 0.0) `(pixels-1st-component ,src-pixels ,offset))) ((component-t ,2nd) ,(if apply-2nd (if removes `(* ,(apply #'+ removes) (pixels-2nd-component ,src-pixels ,offset)) 0.0) `(pixels-2nd-component ,src-pixels ,offset))) ((component-t ,3rd) ,(if apply-3rd (if removes `(* ,(apply #'+ removes) (pixels-3rd-component ,src-pixels ,offset)) 0.0) `(pixels-3rd-component ,src-pixels ,offset))) ((component-t ,4th) ,(if apply-4th (if removes `(* ,(apply #'+ removes) (pixels-4th-component ,src-pixels ,offset)) 0.0) `(pixels-4th-component ,src-pixels ,offset)))) ,@(remove nil (loop for window-offset in corrects for coefficient in coefficients collect (unless (= (* 1.0 coefficient) 0.0) (remove nil `(typed-multiple-value-bind ((component-t ,tmp-1st ,tmp-2nd ,tmp-3rd ,tmp-4th)) (pixels-components ,src-pixels (+ ,offset ,window-offset)) ,(if (< 0 (length ignores)) `(declare (ignore ,@ignores))) ,(if apply-1st `(incf ,1st (the component-t (* ,tmp-1st ,coefficient)))) ,(if apply-2nd `(incf ,2nd (the component-t (* ,tmp-2nd ,coefficient)))) ,(if apply-3rd `(incf ,3rd (the component-t (* ,tmp-3rd ,coefficient)))) ,(if apply-4th `(incf ,4th (the component-t (* ,tmp-4th ,coefficient)))) ))))) (set-pixels-components ,dst-pixels ,offset ,1st ,2nd ,3rd ,4th)))))))))

←詳しくはソースコードを見てね!

Page 21: Common lisp最適化発展

FILMASSEMBLER 21

とりあえずおしまい

1. 空間フィルタとその難しさ ・ボカしフィルタとか輪郭抽出フィルタとか書ける ・とにかく条件分岐が多い ・要素数も多い2. 攻略の手順 ・基本的な手順を踏む ・どれだけ計算を前倒しできるか ・「画素の有無」問題と「可変の窓」問題3. 最適化の実装 ・なんだかよくわからないコードがいっぱい

次回はちょっと今日の様子をみて考えようと思います

ご清聴ありがとうございました