mojavy.com

gearmanクライアントライブラリ cl-gearman

December 16, 2012 at 05:30 PM | categories: gearman, common lisp |

tool

この投稿はCommon Lisp Libraries Advent Calendar 2012の16日目の記事です。

GearmanをCommon Lispから使いたかったのですが、既存のものが見付からなかったのでcl-gearmanというものをつくってみました。というわけで紹介、もとい宣伝をさせてもらおうと思います。

Gearmanとは

今更感はありますが、Gearman自体について簡単に説明しておきます。

http://gearman.org/

Gearmanとは時間のかかる処理を複数のコンピュータに振り分けるように設計されたオープンソースのアプリケーションフレームワークです。Gearmanを利用するアプリケーションでは、client, job server, workerという3つの要素が存在します。 それぞれの役割は以下の通りです。

  • job server: clientから受けとったジョブを適切なworkerに渡す
  • client: ジョブを生成してjob serverに送信する
  • worker: job serverを経由して、clientによってリクエストされたジョブを実行してそのレスポンスを返す

cl-gearmanは、clientとjob server間、及びworkerとjob server間のプロトコルを抽象化したライブラリです。

余談ですが、Gearmanはデータ永続化をしないものだとずっと思っていたのですがmysqlやsqliteに永続化できます。 ジョブキューを使うなら以前はTheSchwartzという選択肢もありましたが、今はGearmanでほとんどの場合に対応できるのではないでしょうか。

インストール

まだquicklispに登録されていないので、githubからソースコードをダウンロードして下さい。(後日申請予定)

https://github.com/taksatou/cl-gearman

cd ~/quicklisp/local-projects
git clone https://github.com/taksatou/cl-gearman.git

使い方

以下サンプルコードです

client

submit-jobはjob serverにジョブを送信し、workerがそれを完了するまで待機します。workerが処理を完了するとその返り値が文字列で返されます。

CL-USER> (cl-gearman:with-client (client "localhost:4730")
           (format t "~a~%" (cl-gearman:submit-job client "hello"))
           (format t "~a~%" (cl-gearman:submit-job client "echo" :arg "foo)))
hello
foo
NIL

以下のようにしてバックグランドでジョブを実行することもできます。バックグランドでジョブを実行するとjobオブジェクトが即座に返されます。 そのjobオブジェクトをつかって、ジョブのステータスを取得することができます。以下の例では見辛いですが、priorityを付けることでジョブの実行順を多少は制御できます。

CL-USER> (cl-gearman:with-client (client "localhost:4730")
           (let* ((job1 (cl-gearman:submit-background-job client "sleep" :arg "1"))
                  (job2 (cl-gearman:submit-background-job client "sleep" :arg "1" :priority :low))
                  (job3 (cl-gearman:submit-background-job client "sleep" :arg "1" :priority :high))
                  (jobs `(:medium ,job1 :low ,job2 :high ,job3)))

             (dotimes (i 5)
               (loop for (k v) on jobs by #'cddr
                  do (format t "~a: ~a~%" k (cl-gearman:get-job-status client v)))
               (sleep 1))))
MEDIUM: (JOB-HANDLE H:hostname:109 IS-KNOWN 1 IS-RUNNING 1 PROGRESS NAN)
LOW: (JOB-HANDLE H:hostname:110 IS-KNOWN 1 IS-RUNNING 0 PROGRESS NAN)
HIGH: (JOB-HANDLE H:hostname:111 IS-KNOWN 1 IS-RUNNING 0 PROGRESS NAN)
MEDIUM: (JOB-HANDLE H:hostname:109 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
LOW: (JOB-HANDLE H:hostname:110 IS-KNOWN 1 IS-RUNNING 0 PROGRESS NAN)
HIGH: (JOB-HANDLE H:hostname:111 IS-KNOWN 1 IS-RUNNING 0 PROGRESS NAN)
MEDIUM: (JOB-HANDLE H:hostname:109 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
LOW: (JOB-HANDLE H:hostname:110 IS-KNOWN 1 IS-RUNNING 0 PROGRESS NAN)
HIGH: (JOB-HANDLE H:hostname:111 IS-KNOWN 1 IS-RUNNING 1 PROGRESS NAN)
MEDIUM: (JOB-HANDLE H:hostname:109 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
LOW: (JOB-HANDLE H:hostname:110 IS-KNOWN 1 IS-RUNNING 1 PROGRESS NAN)
HIGH: (JOB-HANDLE H:hostname:111 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
MEDIUM: (JOB-HANDLE H:hostname:109 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
LOW: (JOB-HANDLE H:hostname:110 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
HIGH: (JOB-HANDLE H:hostname:111 IS-KNOWN 0 IS-RUNNING 0 PROGRESS NAN)
NIL

worker

workerはadd-abilityで実行できるジョブとそのハンドラをあらかじめ登録する必要があります。 ハンドラは2つの引数をとる関数で、第一引数はclientからのデータ、第二引数はjobオブジェクトです。 返り値はprincでフォーマットした文字列としてclientに渡されます。workを実行するとジョブが割り当てられるまでブロックし、1つのジョブの実行が完了すると戻ります。

(cl-gearman:with-worker (worker "localhost:4730")
  (cl-gearman:add-ability worker "hello"
                          #'(lambda (arg job) "hello"))

  (cl-gearman:add-ability worker "echo"
                          #'(lambda (arg job) arg))

  (cl-gearman:add-ability worker "sleep"
                          #'(lambda (arg job)
                              (sleep (parse-integer arg))
                              (format nil "job:~A finished~%" job)))

  (loop do (cl-gearman:work worker)))

エラーハンドリング

ジョブキューを使う上で悩ましいことの一つはエラー処理だと思いますが、Common Lispのコンディションシステムを使えば柔軟に対応できます。 workerのハンドラでは、skip-job, abort-job, retry-job の3パターンのリトライをサポートしています。

例えば、

(cl-gearman:with-worker (worker "localhost:4730") 
  (cl-gearman:add-ability worker "error"
                          #'(lambda (arg job) (error "something wrong")))

  (loop do (handler-bind ((error #'(lambda (c) (invoke-restart 'cl-gearman:skip-job))))
             (cl-gearman:work worker))))

のようにしておけば、エラーが発生したjobを無視することができます。 abort-jobはclientに失敗を通知します。retry-jobは名前の通りハンドラを再度実行します。

対応する処理系

メインに開発を行った環境は ubuntu 12.04, sbcl 1.0.55.0.debian です。 一応以下の処理系で動作確認しました

  • sbcl 1.0.55.0.debian
  • clisp 2.49
  • clozure cl 1.8-r15286M

ffi系のライブラリには依存してないので比較的動かしやすいとは思いますが、動かなければ教えて下さい。

まとめ

以上、拙作のcl-gearmanを紹介しました。Common Lispを始めてまだ半年足らずなのでおかしいところがあるかと思いますが、フィードバックを頂けるとうれしいです。



ウェブエンジニアのためのオンラインツールまとめ

November 20, 2012 at 02:30 AM | categories: web, tips |

tool

The Web engineer's online toolboxというまとめ記事が便利そうだったので、実際に試しつつ抄訳してみました。(一部のコメントと体裁は変えています。)

目次

一覧

RequestBin

httpリクエストを保存するエンドポイントを作ってくれる。

Create a RequestBin のボタンをクリックするとURLが表示されるので、そこをHTTPクライアントからたたくとRequestBin側にリクエスト内容が記録される。 ソースも公開されてるのでローカルで立ちあげることもできる。

githubのwebhookのhelpも参考にどうぞ。

Hurl

httpリクエストを実行してくれる。パーマリンクも作ってくれるので、POSTリクエストもコピペで他の人と共有できる。

httpbin

HTTPリクエスト側でレスポンスのHTTP status codeやレスポンスやリダイレクト、cookieなどを制御できる。HTTPクライアントのテストに便利。

REDbot

HTTPのリソースをチェックしてくれる。問題を検出したら改善案のサジェストもしてくれる。

WebGun

webhookを簡単に作るためのAPIを提供している。現在はまだベータっぽい。

Apify

ウェブページをスクレイピングしてJSON API化してくれる。 CSSセレクタかxpathに名前をつけて設定するだけでよしなにやってくれる。

試しに元記事をAPI化してみた

Unicorn

前述のREDbotはHTTPをチェックしてくれるけど、こちらはHTMLドキュメントがW3Cに準拠してるかをバリデートしてくれる。

Feed validator

こちらはRSSとATOMフィードをバリデートしてくれる。

リンクを再帰的にたどって、重複やリンク切れをチェックしてくれる。

Host tracker

ウェブサイトのモニタリングサービス。定期的にpingして、問題があった場合はメール通知してくれる。

Pingdom Full page test

ウェブページのロード時間を計測、解析してくれる。リクエストを実行するサーバは米国しかないので国内だとちょっと使いづらい。 類似サービスのWeb page testならTokyoが選べる。

HAR viewer

HTTP tracking toolsで生成されたHTTP Archive (HAR)形式のログをビジュアライズしてくれる。

CORS proxy

クロスドメインでjsを実行できるようにヘッダを追加してレスポンスしてくれるプロキシ。

Browserling

ブラウザ上で動くインタラクティブなクロスブラウザ。メジャーなブラウザは大体サポートされている。 動作は重いけど表示確認くらいになら無料のままでも使えそう。

WebSocket Echo Test

WebSocketをテストできるエコーサーバ。

YQL

ウェブサービスからとってきたデータをSQLっぽい言語で整形できる。

Yahoo Pipes

ウェブサービスをGUIでマッシュアップできる。

Apiary

良い感じのREST APIドキュメントをインタラクティブなインスペクタをつかってジェネレートできる。まだベータ。

おわりに

  • いくつかのツールはサーバに負荷をかけてしまう可能性があるので注意してつかってください。
  • 他にもなにかあれば教えてもらえるとうれしいです。


Quicklinks 3

November 14, 2012 at 11:30 PM | categories: quicklinks |

shader

最近はUnityを使う機会を得たのでゲーム関連技術についていろいろ調べてました。

Shadow Algorithms for Real-time Rendering

シェーダーを扱った講義資料らしい。この講義を元にした本がでていて日本語翻訳もあるみたいです。 モデリングは絵心がないとちょっと難しそうだけど、こういうのであれば自分で書いてみたいです。

black-tie

ノイズ生成アルゴリズムというものに興味がわきました。残念ながらこのライブラリはあまりメンテはされてない。

物理演算系ライブラリ

2次元剛体のシュミレーションならChipmunkとかBox2Dあたりで気軽にできそうですが、3次元のやわらかいもの表現するにはちょっとハードルがあがるようです。

物理演算を駆使した3次元リアルぷよぷよがやってみたいです。

mixamo

こういう素材がもっと増えるといいですね。



cl-frontcodingをつくってみた

November 10, 2012 at 08:30 PM | categories: algorithms, common lisp |

lisp

先週くらいに読んだWEB+DB PRESS Vol.42 に載っていたFront Coding という圧縮アルゴリズムを実装してみました。

http://d.hatena.ne.jp/naoya/20080914/1221382329 のパクりです。

ソースはhttps://github.com/taksatou/cl-frontcodingです。

学習目的でつくったので実用的なライブラリではないですが、CLOSやマクロを使いつつパッケージ作成〜テストまで一通りやりました。

CLOSもマクロも基本的な使い方をするだけなら意外と簡単でした。

CLOSとかマクロは本を読んでもいまいちよくわからない上に、深淵なイメージが膨らんで心理的ハードルがあがってしまうだけなので、よくわからないなりになにか作ってみると理解が進んでいいと思います。



Common Lispのdeclareについて

November 09, 2012 at 11:00 PM | categories: common lisp |

lisp

Common Lispのdeclareについて調べた。 変数の型や式に関することをコンパイラに伝えてやるために使うものらしい。

例えば、

(defun add (x y) (+ x y))

(defun add (x y)
  (declare (fixnum x y))
  (the fixnum (+ x y)))

とすれば、引数と戻り値の型を指定できる。

さらに、

(defun add (x y)
  (declare (optimize (speed 3) (safety 0)))
  (declare (fixnum x y))
  (the fixnum (+ x y)))

とすればさらに最適化される。ここまですると、Cで実装した場合と遜色ないくらいになるらしい。

ためしにdisassembleしてみた。処理系はsbcl 1.0.55.0。

最初のバージョンは以下のようになった。すでにある程度最適化されてる? LEA(load effective address)命令は、srcオペランドのアドレスを計算し、そのアドレスをdestオペランドにロードするというものらしい。(参考)

CL-USER> (defun add (x y) (+ x y))

CL-USER> (disassemble #'add)
; disassembly for ADD
; 03C43D5D:       488B55F8         MOV RDX, [RBP-8]           ; no-arg-parsing entry point
;       61:       488B7DF0         MOV RDI, [RBP-16]
;       65:       4C8D1C25E0010020 LEA R11, [#x200001E0]      ; GENERIC-+
;       6D:       41FFD3           CALL R11
;       70:       480F42E3         CMOVB RSP, RBX
;       74:       488BE5           MOV RSP, RBP
;       77:       F8               CLC
;       78:       5D               POP RBP
;       79:       C3               RET
;       7A:       CC0A             BREAK 10                   ; error trap
;       7C:       02               BYTE #X02
;       7D:       18               BYTE #X18                  ; INVALID-ARG-COUNT-ERROR
;       7E:       54               BYTE #X54                  ; RCX

2番目の、型だけを宣言したものは以下のようになった。うーむ、確かに型チェックっぽいコードは増えてるが速くなってるわけではなさそうな感じ。あとで調べる。

CL-USER> (defun add (x y)
           (declare (fixnum x y))
           (the fixnum (+ x y)))

CL-USER> (disassemble #'add)
; disassembly for ADD
; 039D3B73:       488BD1           MOV RDX, RCX               ; no-arg-parsing entry point
;       76:       48D1FA           SAR RDX, 1
;       79:       488BC7           MOV RAX, RDI
;       7C:       48D1F8           SAR RAX, 1
;       7F:       4801C2           ADD RDX, RAX
;       82:       48B80000000000000040 MOV RAX, 4611686018427387904
;       8C:       4801D0           ADD RAX, RDX
;       8F:       48C1E83F         SHR RAX, 63
;       93:       7509             JNE L0
;       95:       48D1E2           SHL RDX, 1
;       98:       488BE5           MOV RSP, RBP
;       9B:       F8               CLC
;       9C:       5D               POP RBP
;       9D:       C3               RET
;       9E: L0:   486BC202         IMUL RAX, RDX, 2
;       A2:       710E             JNO L1
;       A4:       488BC2           MOV RAX, RDX
;       A7:       4C8D1C25B0050020 LEA R11, [#x200005B0]      ; ALLOC-SIGNED-BIGNUM-IN-RAX
;       AF:       41FFD3           CALL R11
;       B2: L1:   488B1557FFFFFF   MOV RDX, [RIP-169]         ; 'FIXNUM
;       B9:       CC0A             BREAK 10                   ; error trap
;       BB:       03               BYTE #X03
;       BC:       1F               BYTE #X1F                  ; OBJECT-NOT-TYPE-ERROR
;       BD:       15               BYTE #X15                  ; RAX
;       BE:       95               BYTE #X95                  ; RDX
;       BF:       CC0A             BREAK 10                   ; error trap
;       C1:       02               BYTE #X02
;       C2:       18               BYTE #X18                  ; INVALID-ARG-COUNT-ERROR
;       C3:       54               BYTE #X54                  ; RCX
;       C4:       CC0A             BREAK 10                   ; error trap
;       C6:       02               BYTE #X02
;       C7:       08               BYTE #X08                  ; OBJECT-NOT-FIXNUM-ERROR
;       C8:       95               BYTE #X95                  ; RDX
;       C9:       CC0A             BREAK 10                   ; error trap
;       CB:       04               BYTE #X04
;       CC:       08               BYTE #X08                  ; OBJECT-NOT-FIXNUM-ERROR
;       CD:       FED501           BYTE #XFE, #XD5, #X01      ; RDI

最後の最適化の宣言をつけたものは以下のようになった。たしかに速そうにはなった。

CL-USER> (defun add (x y)
           (declare (optimize (speed 3) (safety 0)))
           (declare (fixnum x y))
           (the fixnum (+ x y)))

CL-USER> (disassemble #'add)
; disassembly for ADD
; 034DD10F:       4801FA           ADD RDX, RDI               ; no-arg-parsing entry point
;       12:       488BE5           MOV RSP, RBP
;       15:       F8               CLC
;       16:       5D               POP RBP
;       17:       C3               RET

ついでにCで書いた以下の関数もdisassebleしてみた。

int add(int x, int y) {
    return x + y;
}

cc -c a.c && objdump -d a.o とすると、

a.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <add>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   89 75 f8                mov    %esi,-0x8(%rbp)
   a:   8b 45 f8                mov    -0x8(%rbp),%eax
   d:   8b 55 fc                mov    -0x4(%rbp),%edx
  10:   01 d0                   add    %edx,%eax
  12:   5d                      pop    %rbp
  13:   c3                      retq

cc -O3 -c a.c && objdump -d a.o なら、

a.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <add>:
   0:   8d 04 37                lea    (%rdi,%rsi,1),%eax
   3:   c3                      retq

まあとりあえず単純に命令数だけでいうと、 素のSBCL > 素のGCC > 最適化したSBCL > 最適化したGCC という感じにはなったので、CよりLispのほうが速い(場合もある)というふれこみは嘘ではなさそうですね。



About Me

pic
mojavy

Recent posts






Categories



Badges