既存のテンプレートエンジンの問題点と再設計 2015-03-02 Lisp Common Lisp Arrows 既存のテンプレートエンジンの問題点と再設計、あるいはArrowsについて Clack Meet Up #1 2015-03-05 @サムライト === About Me κeen @blackenedgold Github: KeenS 野生のLisper Lisp, Ruby, OCaml, Shell Scriptあたりを書きます === Template Engines === Existing Architeture (リクエスト) アプリ 引数を計算 テンプレートに引数を渡す レスポンスをレンダリング レスポンスをサーバに渡す (レスポンス) === Rendering? 結合した文字列はクライアントに返ったらその後はゴミ GCへ負荷がかかる (後述) クライアントにとって1つの文字列である必要はない むしろ返せる部分だけ先に返した方が得(後述) === GC Pressure (SBCL) 世代別Copy GC 結合した文字列は比較的大きい alloc_spaceに入らない大きさならアロケートが遅い GCを頻繁に起動してしまう 16KBを越えると特別扱いされて遅い/メモリを喰う LispのWebアプリはレスポンスタイムの分散が大きい(要出展) 参考: SBCL GENCGC @ x86 Linux Split Response 例えば、こんなの <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us"> <head> <link rel="stylesheet" href="http://localhost:1313//reveal.js/lib/css/xcode.css"> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> .... </script> </head> <body class="li-body"> <header>{{ header }}</header> {{ body }} ... === Split Response {{ header }}の前に先頭から<header>までを返す {{ header }}を返す </header>を返す {{ body }}を返す … === Pros of Split Response headerを計算してる間にクライアントにhead部分が渡る 先に<link>や<script>を要求出来る サーバ側のスループットやレスポンスタイムは変わらないが クライアントのレンダリング完了までの時間は大幅に短縮出来る 文字列を結合する必要がない 定数部分については長さが判ってるので最適化し易い === How TEs Work Compilation テンプレート文字列 パーサ 抽象構文木 コードエミット レンダリング関数 === How TEs Work Rendering レンダリング関数 引数 文字列 === Misc Problems サーバに渡すのは文字列なのにソケットに書き込む時はオクテット? 文字列で返す?オクテットで返す?(デバッグがー) オクテットの変換はいつ? ストリームが遅い? POSIX APIが使える"なら"fdの方が速い? ユーザが用意したバッファに書き出したい? テンプレートに渡す引数が定数文字列なら畳み込める筈? リクエストの度にテンプレートパースするのは筋悪だけど開発中は毎回コンパイルするのは面倒? === Arrows Template flies like an arrow === Arrows KeenS/arrows 現在開発中のテンプレートエンジン 複数のテンプレートが選べる(予定) 複数のバックエンドが選べる バックエンドに依ってはnon-consing === How Arrows Works Compilation テンプレート文字列 + 定数引数 パーサ(default, cl-emd互換…) 抽象構文木 最適化(const folding, concat, convert…) コードエミット レンダリング関数(string, octets, stream, fast-io …) === How Arrows Works Rendering レンダリング関数 引数 文字列、オクテット列、ストリーム書き出し、fast-io… === How compiled <h1>Hi {{var name}}!</h1> を (compile-template-string :xxx "<h1>Hi {{var name}}!</h1>" ()) とコンパイル Stream backend (lambda (stream &key name) (write-string "<h1>Hi " stream) (write-string (encode-for-tt (princ-to-string name)) stream) (write-string "!</h1>" stream)) ほとんどアロケートしない === Octet backend (lambda (&key name) (with-fast-output (buffer) (fast-write-sequence #.(string-to-octets "<h1>Hi ") buffer) (fast-write-sequence (string-to-octets (encode-for-tt (princ-to-string name))) buffer) (fast-write-sequence #.(string-to-octets "!</h1>") buffer))) === How optimized <h1>Hi {{var name}}!</h1> を (compile-template-string :stream "<h1>Hi {{var name}}!</h1>" '(:known-args (:name "<κeen>"))) とコンパイル How optimized variable folding (lambda (stream) (write-string "<h1>Hi " stream) (write-string (encode-for-tt "<κeen>") stream) (write-string "!</h1>" stream)) 既知の引数は畳み込む 文字列ならprinc-to-stringしない === How optimized const folding (lambda (stream) (write-string "<h1>Hi " stream) (write-string "<κeen>" stream) (write-string "!</h1>" stream)) 定数のエスケープはコンパイル時に済ませる === How optimized append sequence (lambda (stream) (write-string "<h1>Hi <κeen>!</h1>" stream)) 複数シーケンスの書き出しは1つにまとめる === Further Ideas 引数計算の遅延 引数計算の並列化 非同期化 HTML compction === Further Ideas 引数計算の遅延 nameの計算が重いときに先に"<h1>Hi "を返す。 nameは必要になったら値を計算する(Promise パターン) (lambda (stream &key name) (write-string "<h1>Hi " stream) (write-string (encode-for-tt (princ-to-string name)) stream) (write-string "!</h1>" stream)) === Further Ideas 引数計算の並列化 nameの計算が重いときに先に"<h1>Hi "を返す。 nameは並列に計算して必要になったら値を要求する(Futureパターン) (lambda (stream &key name) (write-string "<h1>Hi " stream) (write-string (encode-for-tt (princ-to-string name)) stream) (write-string "!</h1>" stream)) === Further Ideas 非同期化 単純にwriteを非同期にする 他にFutureもブロックするので非同期Futureを使う === Further Ideas HTML compction <ol> <li> item 1 </li> <li> item 2 </li> <li> item 3 </li> </ol> を <ol><li>item 1</li><li>item 2</li><li>item 3</li></ol> DOM構造が変わってしまう === TODOs 設計上複数シンタックスをサポート可能だがまだしてない 既存のテンプレートエンジンとの比較ベンチマーク 高速化 多機能化 テンプレート 最適化 バックエンド clackとの連携 clackのAPIはメモリアロケーションが多めに必要になる === Summary 既存のテンプレートエンジンは非効率 メモリを無駄遣いしていた ユーザーのことを考えてなかった 新しいテンプレートエンジンを設計した メモリアロケーションをあまりしない ユーザー側の速度まで考慮した 柔軟