common lisp最適化発展

Post on 14-Jun-2015

799 Views

Category:

Engineering

4 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Common Lisp最適化発展TOYOZUMI Kouichi

@TOYOZUMIKouichi

FILMASSEMBLER

Copyright 2014 TOYOZUMI Kouichi and FILMASSEMBLER all rights reserved.

FILMASSEMBLER 2

今日話すこと

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

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

はじめに

FILMASSEMBLER 3

輪郭抽出とかできます

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

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

FILMASSEMBLER 4

空間フィルタの内実

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

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

FILMASSEMBLER 5

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

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

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

FILMASSEMBLER 6

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

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

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

FILMASSEMBLER 7

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

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

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

攻略の手順

FILMASSEMBLER 8

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

0 1 0

1 -4 1

0 1 0

-1 -1 -1

-1 8 -1

-1 -1 -1

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

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

FILMASSEMBLER 11

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

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

攻略の手順

FILMASSEMBLER 12

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

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

攻略の手順

FILMASSEMBLER 13

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

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

攻略の手順

・赤マスの画素の取得を試みず、全体の係数の和が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

・係数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

FILMASSEMBLER 16

「可変の窓」問題の解決

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

攻略の手順

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

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

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

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

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

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

FILMASSEMBLER 21

とりあえずおしまい

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

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

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

top related