Common lisp最適化応用

19
Common Lisp最適化応用 TOYOZUMI Kouichi @TOYOZUMIKouichi FILMASSEMBLER Copyright 2014 TOYOZUMI Kouichi and FILMASSEMBLER all rights reserved.

description

 

Transcript of Common lisp最適化応用

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. MAP-IMAGEとその実装のおさらい2. DEFUNSAFE2が「コケる」とき3. SIMD演算による高速化4. コンパイラマクロと関数定義情報を用いた最適化

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

はじめに

Page 3: Common lisp最適化応用

FILMASSEMBLER 3

MAP-IMAGE

・画像を「画素のリスト」に見立ててMAPを行います ・引数は画像と関数 ・関数は「画素をとって画素を返す」関数・例えばこんなことができます ・色の反転 ・簡単な可視化 ・合成

MAP-IMAGEとその実装のおさらい

Page 4: Common lisp最適化応用

FILMASSEMBLER 4

その実装

・実際に「画素を関数に渡して画素を受け取る」と効率が悪い ・使い捨ての画素を何度も確保しなければならない・画素を構成する成分値を多値として直接やりとりする ・レジスタに直接値が載るので効率が良くなる

・コードが冗長になるのでDEFUNSAFE2というマクロで隠蔽する

MAP-IMAGEとその実装のおさらい

(defunsafe2 invert-pixel pixel ((pixel pixel)) (pixel (invert-component (pixel-red pixel)) (invert-component (pixel-green pixel)) (invert-component (pixel-blue pixel)) (pixel-alpha pixel)))

(defunsafe map-image image ((function func) (image image)) (typed-let1 image result (make-image :size (image-size image)) (loop for offset fixnum from 0 below (image-length image) do (typed-multiple-value-bind ((component-t red green blue alpha)) (funcall func (image-components image offset)) (set-image-components image offset red green blue alpha))) result))

Page 5: Common lisp最適化応用

FILMASSEMBLER 5

DEFUNSAFE2の限界

・PIXEL型をCOMPONENT型に変える ・単純にPIXEL型の変数を4つのCOMPONENT型の変数に  ・LET式やLET1式で束縛している局所変数も同様 ・(PIXEL ~で返している画素は(COMPONENTS ~で置換  ・他の関数から画素を返させる場合  ・結果はCOMPONENT型の多値なのでこれを束縛  ・この関数のCOMPONENT型を返すバージョンはあるか?   ・問い合わせて存在すればどんどん置換  ・DEFUNSAFE2で定義された関数はこの検索の対象になる   ・あとは積み上げていけばおっけー

DEFUNSAFE2が「コケる」とき

Page 6: Common lisp最適化応用

FILMASSEMBLER 6

DEFUNSAFE2がコケる要因

・型指定が中途半端で追い切れなくなっちゃった・想定外のマクロで値を束縛するようなコード ・俺の預かり知らないマクロで値を束縛しないでくれ ・MACROEXPANDで全部バラしちゃう作戦はまだやってない  ・デバッグがすごく大変そう・例:関数AのCOMPONENT型を返すバージョン関数C-A ・FUNCTION->COMPONENT-FUNCTIONで照会 ・存在すればまっとうなDEFUN式が返る ・存在しないとおかしなDEFUN式が返る・そもそも自分が論理的に満足なコード書ける自信はない ・というわけで「コケたら」というコードを書いています

DEFUNSAFE2が「コケる」とき

Page 7: Common lisp最適化応用

FILMASSEMBLER 7

SIMD演算について

・Single Instruction Multiple Data演算、ということで ・単命令複情報演算、すなわち、一つの命令で複数のデータを演算 ・同じタイプのデータを大量処理する画像処理向き ・実は転送にも効果を発揮する・・・・・・はず・あんまり複雑なことはできない、だってアセンブラの命令ですから・四則演算と論理演算とあとなんか文字列処理とかできるかも ・実数の冪乗の計算を四則演算の組み合わせで実装・・・・・・(ヤメテ) ・とは言っても画像処理では充分です

SIMD演算による高速化

Page 8: Common lisp最適化応用

FILMASSEMBLER 8

Common LispにおけるSIMD演算

・現時点で明示的にこれを利用できるのはSBCLのみ(と認識している) ・IntelのStreaming SIMD Extensions(SSE)が使えます ・cl-simdというライブラリを使います  ・SSEのイントリンシック命令が使えます  ・https://github.com/angavrilov/cl-simd ・SBCL側ではSB-SIMD-PACKを有効化するための修正が必要 ・レジスタ長は128bitで動作確認  ・AVXの256bitレジスタは使えないみたい?・MayakaではSSE3と4の命令をいくつか使ってます ・自分でてきとーに命令を追加しました  ・転送や最大値最小値の取得などにもつかってます  ・ご多分に漏れず「多分」動いているものと思われます

SIMD演算による高速化

Page 9: Common lisp最適化応用

FILMASSEMBLER 9

やらなきゃいけないことデータ編

・転送命令はアライメントが揃っているとより高速なものを使えます ・というわけでメモリ確保時に揃えます ・Mayakaは画像IO担当のtsugumiさんに任せてます・128bitのレジスタがリストになってて二つのリストにMAPする ・16bitのデータ8個あるいは32bitのデータ4個 ・従ってメモリ上のデータの並び方に注意しないといけません  ・RGBARGBARGBARGBA....という並びにすると結果はカオス  ・RRRRRRRRGGGGGGGG....とするとみんな幸せ ・全体の長さを128bitで分割できるようにしないと末尾で問題発生

SIMD演算による高速化

posix_memalign(&pointer, 32, (((size_t)lcm(width * height * 4, 8)) * (size_t)sizeof(int16_t)));

Page 10: Common lisp最適化応用

FILMASSEMBLER 10

やらなきゃいけないこと演算編

・IFとかCONDなんてものはない ・比較結果がマスクになるのでそれをつかってコネコネします・例:

 ・1 .入ってきたデータ ・2. THENの結果 ・3. ELSEの結果 ・4. PREDから作られたマスク ・5. 4.の反転 ・6. 2.と4.をAND ・7. 3.と5.をAND ・8. 6.と7.をOR

・やってられない

SIMD演算による高速化

(if (< 5 n) (+ n 5)

n) 1, 5, 9, 2, 8, 6, 3, 0

0, 0, F, 0, F, F, 0, 0 F, F, 0, F, 0, 0, F, F

6, A, E, 7, D, B, 8, 5 1, 5, 9, 2, 8, 6, 3, 0

0, 0, E, 0, D, B, 0, 0 1, 5, 0, 2, 0, 0, 3, 0 1, 5, E, 2, D, B, 3, 0

(or (and (pred d) (then d)) (and (not (pred d)) (else d)))

Page 11: Common lisp最適化応用

FILMASSEMBLER 11

伝家の宝刀マクロで隠蔽

・CPIFとかCPCONDとか接頭辞CPをつけたマクロを作る ・これにてIFの記述が冗長になる問題は回避 ・CPはパックされた成分値COMPONENT-PACKの頭字語・あとはDEFUNSAFE2が成分値版に加えて成分値組版も作る ・アルゴリズムは画素版から成分値版を作るときとさほど変わらず ・MAP-IMAGEも専用版を作る・どうやっても変換不可能なもの(例えばガンマ補正) が出てくるのでそういうのが出てきたら「あきらめる」処理 ・動くようにできないことはない  ・しかし変換が難しい  ・パックされた値からそれぞれを取り出して再格納という手間   ・今のところ成分値方式でいいや

SIMD演算による高速化

Page 12: Common lisp最適化応用

FILMASSEMBLER 12

まだ残る問題

・DEFUNSAFE2を使わないと最適化がなされない ・末端ではCommon Lisp標準のDEFUNとLAMBDAで  コトを済ましたい・MAP-IMAGEに渡された関数はインライン展開されない ・名前のついた関数オブジェクトは基本コンパイルされてる ・LAMBDAで渡された無名関数だってコンパイル可能・どのMAP-IMAGEを使うかを早めに自動的に決めたい ・明示的に指定しないと、どれを使うか決定する処理が必要  ・この処理、大抵はコンパイル時に可能  ・実行時に処理すればそれだけ遅くなる

コンパイラマクロと関数定義情報を用いた最適化

Page 13: Common lisp最適化応用

FILMASSEMBLER 13

「あとからDEFUNSAFE2」

・DEFUNSAFE2がやっている最適化の実現 ・少なくとも画素版の関数のソースコードが必要 ・これさえあれば最適化がかけられる・簡単な例:LAMBDAでMAP-IMAGEに渡されている ・これはコンパイラマクロでLAMBDA式がコンパイルされる前に  式として取得してしまえばよい  ・この式は確実に「画素をとって画素を返す」式  ・あとはこれに最適化をかけてコンパイル・問題の例:DEFUNでコンパイルされた関数 ・MAP-IMAGEに渡されているのは単なるシンボル ・手に入るのはコンパイル済関数 

コンパイラマクロと関数定義情報を用いた最適化

Page 14: Common lisp最適化応用

FILMASSEMBLER 14

関数定義情報の取得

・SBCLはコンパイルされた関数がコンパイルされた時の式を 持っている「ことがある」 ・持っていないこともあるが、条件は不明 ・いろいろ取得方法があるが、条件は不明 ・他の処理系は調査中・FUNCTION-LAMBDA-EXPRESSIONで取得できるのはごく一部 ・LispWorks Personal Editionでも取得できた ・しかし取得できると話ははやい・べつのやり方(全部引数一つです) ・SB-DI::FUN-CODE-HEADERしたものを ・SB-KERNEL:%CODE-DEBUG-INFOして ・SB-C::COMPILED-DEBUG-INFO-SOURCEしたうえで ・SB-C::DEBUG-SOURCE-FORMすると、出てくる

コンパイラマクロと関数定義情報を用いた最適化

Page 15: Common lisp最適化応用

FILMASSEMBLER 15

関数定義情報の取得コンパイラマクロと関数定義情報を用いた最適化

MAYAKA> (function-properties #'rgb->hsb)RGB->HSB(RED GREEN BLUE)(COMPONENT-T COMPONENT-T COMPONENT-T)COMPONENT-T(LAMBDA (RED GREEN BLUE) (DECLARE (OPTIMIZE (SPEED 3) (SPACE 0) (DEBUG 0) (SAFETY 0)) (COMPONENT-T RED GREEN BLUE) (IGNORE)) (TYPED-MULTIPLE-VALUE-BIND ((COMPONENT-T MAX-COMPONENT MIN-COMPONENT)) (MAX-AND-MIN-COMPONENT RED GREEN BLUE) (VALUES (RGB-AND-MAX-MIN->HUE RED GREEN BLUE MAX-COMPONENT MIN-COMPONENT) (MAX-AND-MIN-COMPONENT->SATURATION MAX-COMPONENT MIN-COMPONENT) MAX-COMPONENT)))

関数 L4S:function-properties(defun function-properties (func))

関数FUNCの名前と引数リスト、引数の型リスト、返り値型および定義を多値として返します。各値が取り出せない場合はNILを返します。 なお返された定義はLAMBDA式でありかつDECLARE式内のOPTIMIZE式は含まれないことがあります。

Page 16: Common lisp最適化応用

FILMASSEMBLER 16

それでも残る問題

・以下の問題はコンパイラマクロと関数定義情報を用いて解決 ・MAP-IMAGEに渡された関数はインライン展開されない ・どのMAP-IMAGEを使うかを早めに自動的に決めたい

コンパイラマクロを用いれば、すべてがコンパイル時に行われるので場合によっては処理の実時間を短縮できる

コンパイラマクロと関数定義情報を用いた最適化

Page 17: Common lisp最適化応用

FILMASSEMBLER 17

それでも残る問題

・MAP-IMAGEに渡された関数はインライン展開されない ・FLETに関数の実装を置き直し、インライン展開させる

コンパイラマクロと関数定義情報を用いた最適化

(define-compiler-macro c-map-image1 (&whole form image func) (map-image-function-lazy-eval func form (evaled-func) (if-take-func-declaration evaled-func form (declaration) ;; 定義を抽出できた場合 (with-gensyms (c-map-image1-func destination src-pixels dst-pixels) (let1 symbol (gensym) (multiple-value-bind (lmd other) (lambda-and-other declaration symbol) `(bind-destination-and-each-pixels ,image (,destination ,src-pixels ,dst-pixels) (funcall (typed-lambda ((pixels src-pixels dst-pixels) (length-t start end)) ,(replace-symbol other symbol `(flet ((,c-map-image1-func ,@(cdr lmd))) (loop for offset fixnum from start to end do (pixels-function-applied-components (src-pixels) offset ,c-map-image1-func nil (1st 2nd 3rd 4th) (set-pixels-components dst-pixels offset 1st 2nd 3rd 4th)))))) ,src-pixels ,dst-pixels 0 (image-length ,image)) ,destination)) )))))

Page 18: Common lisp最適化応用

FILMASSEMBLER 18

それでも残る問題

・どのMAP-IMAGEを使うかを早めに自動的に決めたい ・「あとからDEFUNSAFE2」をやり、最も速い使える関数を返す

コンパイラマクロと関数定義情報を用いた最適化

(defun map-image-fastest-function (n func) "関数FUNCからMAP-IMAGEで使うための最も高速な関数とその関数に引数にとる型を多値として返します。" ...)

(defun map-image2 (source destination func) "画像SOURCEと画像DESTINATIONを構成するすべての画素にFUNCを適用し、その結果から新たな画像を作成して返します。" (multiple-value-bind (actual-func mode) (map-image-fastest-function 2 func) (cond ((equal mode 'pixel-t) (p-map-image2 source destination actual-func)) ((equal mode 'component-t) (c-map-image2 source destination actual-func)) #+sb-simd-pack ((equal mode 'component-pack) (cp-map-image2 source destination actual-func)))))

(define-compiler-macro map-image2 (&whole form source destination func) (let1 evaled-func (ignore-errors (eval func)) (if (and evaled-func (functionp evaled-func)) (multiple-value-bind (actual-func mode) (map-image-fastest-function 2 evaled-func) (cond ((equal mode 'pixel-t) `(p-map-image2 ,source ,destination ,actual-func)) ((equal mode 'component-t) `(c-map-image2 ,source ,destination ,actual-func)) #+sb-simd-pack ((equal mode 'component-pack) `(cp-map-image2 ,source ,destination ,actual-func)))) `,form)))

Page 19: Common lisp最適化応用

FILMASSEMBLER 19

とりあえずおしまい

1. MAP-IMAGEとその実装のおさらい ・画像を画素のリストとしてあつかってMAPします2. DEFUNSAFE2が「コケる」とき ・いくつか想定外の事態がありますよ3. SIMD演算による高速化 ・SBCLとCL-SIMDを使ってSSEのイントリンシック命令が使える ・いろいろ手をうってやらなきゃならないことがある4. コンパイラマクロと関数定義情報を用いた最適化 ・関数の定義時の式を実は取り出せる ・これを使ってコンパイラマクロと組み合わせると  より強い最適化をかけられる次回は単なる型付けやオプション付加に止まらないコード生成と変形を伴う最適化について解説します

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