2014-02-27

LZ4 の近況を確認してみる

lz4-ruby の開発を進めるために、LZ4 の近況を確認してみたところ、 それなりに活発に開発されているようで変更点が多かったため、メモを兼ねてエントリにまとめてみました。

r76 から r113 までの変更履歴を追う形で、主だった変更を列挙していきます。

API が増えた

r76 の時点ではマクロを含めて 6 個だった API が、r113 では obsolete を除いても 36 個と大幅に増えました。

LZ4 の基本機能は以前とはそう変わらないものの、後述するストリーム処理用 API など、 利用シーンごとに適した API を拡充しているようです。

liblz4 が作成されるようになった

r111 より。

以前の LZ4 は Makefile はあれど make してもライブラリは生成されず、 そのためアプリケーションから LZ4 の圧縮・伸長機能を利用しようとすると、 アプリケーションのビルド時に lz4.c などをあわせてコンパイルし、 同ソースコードのオブジェクトファイルをリンクする必要がありました。

この方法では LZ4 のリビジョンをアップデートしてアプリケーションを 再ビルドする手順がちょっと複雑になるデメリットがありましたが、 r111 で更新された Makefile によって、ライブラリ liblz4.a / liblz4.so が生成されるようになり、この問題は多少緩和されたのではないかと思います。

ただ、make install でのライブラリ / ヘッダファイルのインストールには 対応しているものの、 yum やら apt-get でインストールできるわけではないので、 依存パッケージの自動的な解決などはまだ期待できません。

ストリーミング処理に対応した

r102 より。

以前の LZ4 では、圧縮したい単位で圧縮対象のデータを API に渡し、また圧縮後のデータを記録する ヒープを十分に割り当てした上でAPI を呼び出す必要がありました。 この API 構成は zlib などの API とは大きく異なり、利用ケースによっては使い辛いこともあったようです。 (圧縮・伸長の時間性能を稼ぐためには仕方のない API 設計ではあったのですが…)

r102 で導入されたストリーム処理の API によって、ストリーミング的な圧縮処理を実現することができるようになりました。 (詳しい利用方法などは確認中です…)

LZ4 HC の圧縮レベルを指定できるようになった

r113 より。

LZ4 には、時間的な圧縮処理性能を犠牲にしつつ、空間効率的によりよい (= 圧縮率を重視した) 結果を得るためのモード、 LZ4 HC が存在します。

直近のリビジョンである r113 にて、この LZ4 HC に「圧縮レベル」パラメータが追加され、 圧縮率と処理時間のトレードオフのバランスを開発者がチューニングできるようになりました。

圧縮・伸長の時間効率がさらに向上した

LZ4 の一番の特徴である高速な圧縮・伸長処理は継続的に改善されているようです。 特にプラットフォームやコンパイラごとの細かな最適化がなされている印象です。

まとめ

更新履歴 を見るとそれなりに開発が継続されているようですし、 イシュー を見てもまだやるべきことは残されているようなので、今後も開発は継続されていくことでしょう。まだまだ要注目ですね!
23:11No comments

2014-01-21

Ubuntu 12.04 LTS 上で OpenCV 1.0.0 をソースコードからビルド&インストールする

はじめに

2.x 系が公開されているこのご時世に、やんごとなき理由により OpenCV 1.0.0 をソースコードビルド&インストールしなければならない状況、エンジニアの皆さんなら一度や二度は経験していますよね。ね?

今回、Ubuntu 12.04 LTS 上で OpenCV 1.0.0 を苦労しながらソースコードからビルドしてみたので、そのメモを残しておきます。

1. パッケージのインストール

パッケージのインストールなどを先に済ませます。個人的な好みで aptitude を使っていますが、 apt-get でも同様にインストールできるはずです。

$ sudo aptitude install build-essential

# コンパイルエラー/リンクエラーを阻止するために、これはインストール必須。
$ sudo aptitude install libv4l-dev
$ sudo ln -s /usr/include/libv4l1-videodev.h /usr/include/linux/videodev.h

# 必要に応じて、取捨選択する
$ sudo aptitude install libjpeg-dev libpng-dev

2. OpoenCV 1.0.0 のダウンロード

ブラウザから、こちら のページより tar.gz ファイルをダウンロードし、適当な作業用ディレクトリ上に展開しておきます。

3. OpenCV をビルドする

まずは作業用ディレクトリ上で

$ ./configure

します。問題なく実行完了がしたところで、 make ... をする前に、こちら のページの "1) Error message:" にあるとおり、 cxcore/include/cxmisc.h の条件式なしの #elif#else に書き換えておきます。この書き換えをしておかないと、早々にコンパイルエラーが発生してしまいます (残りの 2, 3 の Error message については、 libv4l-dev パッケージのインストールとその後のシンボリックリンク作成で対処できるようなので、スキップして構いません)。

cxmisc.h の書き換えを終えたところでようやく

$ make
$ sudo make install

でインストールは完了します。

19:02No comments

2013-11-18

第4回 #渋谷java で JUnit のオブジェクト等価比較処理を怠ける (ために Matcher を頑張る) トークをしてきました

通算3回目の参加・発表ということで大変お世話になっている 第4回 #渋谷java でお話してきました。5 分の LT では語れなかった内容が多々あるので、それらを盛り込んでエントリを残しておきます。

発表資料は以下です。

発表で使用する予定だったデモコードは komiya-atsushi/shibuya-java4 に置いておきました。

オブジェクト等価比較を怠けたくなった背景

事の発端

元々は SAStruts なとあるお仕事において、JSON を返却する Web API の Action クラスの各メソッドを JUnit でテストしたい、というのが事の発端でした。

このテストの本来の理想的な姿は、Tomcat などの AP サーバを経由して HTTP で実際に Web API を呼び出し、その結果のレスポンス の JSON が期待している内容と同一かどうかを比較する… というものでした。しかし、この構成を JUnit / S2JUnit4 で実施するにはなかなか難儀することが予想された(ぶっちゃけ面倒だった)ため、早々に Action クラスのメソッドを直接呼び出すことで得られる JSON 文字列を (デシリアライズして) アサーションする、という現実的な方針に切り替えました。

そういうわけで、Web API から返されるちょっと複雑なデータの JSON を Java のオブジェクトとして取り扱いつつ、それに対して期待している結果と等しいかどうかの等価比較 ( assertThat(actual, is(expected)); ) をすることが求められたわけですが… まあ Web API の戻り値として返すようなオブジェクトのクラスに Object#equals() がオーバーライド実装されていることなんてそうあるわけでもなく、ただひたすらにオブジェクトにプロパティ経由で値を参照して assertThat(actual.getHoge(), is(expected.getHoge()); する、みたいなテストコードの記述を強いられる残念な状況が生じたわけです(さすがに、さしみたんぽぽ作業をただひたすらに続けられるほど、僕はできているエンジニアではなかったので、2, 3 行試しに書いてみたところで見事に崩折れました)。

Object#equals() の実装

assertThat() の羅列が本当に羅列すぎて生きるのが辛いので、本来は equals() の実装は不要だけれども JUnit でのテストのために致し方なく本末転倒的にオーバーライドすることも念の為に考えてみました。

幸いにして、 Apache commons-langEqualsBuilder を使うと、比較的容易に equals() を実装することができます (commons-lang については こちらの資料 が参考になります)。特に、 EqualsBuilder#reflectionEquals() を使うと驚くほど簡単に equals() を実現できます。

しかし一方で、 EqualsBuilder を使って equals() を実装してアサーションを行うようにすると、アサーションに fail したときのメッセージが以下のように役立たずなメッセージになる、というデメリットが存在します。

java.lang.AssertionError: 
Expected: is <foo.bar.HogeBean@9a8d9b>
     but: was <foo.bar.HogeBean@22c6bb6c>

Matcher による解決の試み

Object#equals() を実装していないクラスのオブジェクトに対して等価比較なアサーションをしたい場合、 org.hamcrest.BaseMatcher クラスを継承実装するという手があります。この方法を利用すると、

  • 等価比較だけでなく、値の大小比較などのきめ細かいアサーションが可能になる。
  • fail したときの mismatch メッセージに不一致となった内容や各種コンテキスト情報などを載せることができ、fail メッセージからの不具合の原因追求が容易になる。
  • assertThat() まわりの記述がすっきり・シンプルになる。

というメリットを得ることができます。ただ、Web API のようにエンドポイントごとに返却されるオブジェクトが異なる場合などはエンドポイントの数だけクラスが実装されていることもあり、そのクラスの数だけ BaseMatcher クラスを継承実装するというのは現実的ではありません (そもそもの BaseMatcher の実装コストがかかる、というデメリットもあります)。

汎用的な等価比較用 Matcher を目指す

そういうわけで、「equals() を実装する」わけでもなく「個別 に Matcher を用意する」わけでもない方法で解決するアプローチとして、汎用的に利用できる等価比較用の Matcher である IsEquivalentTo を作成しました。

この Matcher を利用することで、

  • equals() の実装を必要とせず
  • もちろん独自 Matcher も実装不要で
  • assertThat() まわりのコードも比較的シンプルに保ち
  • 等価比較したくないプロパティ / フィールドも明示できる

テストコードの記述が可能になります! (なる予定です!)

Matcher の汎用化、というわけで、個別に Matcher を実装したときと比べてきめ細かなアサーションは実現が難しくなりますが、等価比較できるところだけをこの汎用化 Matcher を利用して比較し、等価比較ではないアサーションは別途 assertThat() の羅列などで対処する、というアプローチもとれることでしょう。

技術的だったり細かい話

IsEquivalentTo の実装では、以下の工夫を盛り込みつつ、等価比較の汎用化を実現しています。

  • 再帰的なオブジェクトのトラバース ... オブジェクトを木構造なデータ構造と見立て、List / Map / 配列 / その他オブジェクト、の 4 種類ごとに等価比較の戦略を分けています。List / Map / 配列であれば、単純に保有しているオブジェクトに対して等価比較をするようにします。

  • オブジェクトの等価比較 ... Object#equals() をオーバーライド実装していないオブジェクトの場合、単純に equals() による等価比較をしてしまっては残念な結果になることは目に見えているので、以降に示す「プロパティの参照」「public フィールドの参照」を試すようにしています。一方で equals() をオーバーライド実装しているようであれば、その equals() を呼び出すようにします。

  • プロパティの参照 ... JavaBeans の規約に従って定義されているプロパティは、JavaBeans APIjava.beans.PropertyDescriptor を利用して、getter メソッド経由で値を読み出すようにしています。

  • public フィールドの参照 ... 最近だと、JavaBeans の規約にあえて従わず、public にフィールドを定義してクラスの外にフィールドをさらけ出すような実装を見かけることも、実際にそのような実装をすることも多くなってきました (MessagePack が要求するクラスがまさにそのようなフィールド構成ですね)。そのため、public なフィールドについてはリフレクション API を利用して、プロパティの場合と同じようにその内部の値を取得・参照するようにしています。

  • 例外的に扱いしたいプロパティ / フィールドなどの指定 ... テスト実行した日時の情報など、不定な値がオブジェクトに含まれる場合は、その値を無視して等価比較したくなるものです。そのようなケースを考慮して、 JSONPath 的な記法で等価比較したくない要素を指定できるようにしています。

  • 比較的親切な fail 時の mismatch メッセージ ... JUnit のテストケースを作成する上で重要なことの一つに、テストケースが fail したときに「何が原因で fail したのか?」ができる限り把握できるメッセージを出力することがあります。 IsEquivalentTo の実装では、オブジェクト階層を意識して、JSONPath 的な表現で、「オブジェクトのどこがおかしいのか?」が把握可能なメッセージ出力ができます。

会場参加者への逆質問

今回話題にした等価比較の問題、他の人はどういうアプローチをとっているのかなー? と気になったので、会場参加されている方々に 逆質問してみたところ、

  • equals() が実装されていることは確かに少ないけど、 toString() が実装されていることは結構あるので、文字列比較で済ませている。
  • 割り切ってテストはしない。

との回答をいただきました。不定な値がオブジェクトに紛れることがなければ、確かに toString() で比較できますね… 参考になります。

その他、感想などなど

  • build.xml と JSP はそろそろ滅んで欲しい!
  • Groovy / Grails に俄然興味が湧いてきた!
  • TokyoWebmining の新たな講師がゲットできそう!
  • 資格試験 (統計検定) の前日に発表予定を入れるのはもうやめよう…

最後に、会場提供の BizReach さん、そして主催の @seri_k さん、お疲れさまでした&ありがとうございました!!

9:21No comments

2013-09-03

第 4 回 #DSIRNLP データ構造と情報検索と言語処理勉強会でお話してきました。

3週間ぐらい前かつ発表前後は業務的に憤死しててもおかしくない時期ではありましたが、ありがたいことに主催者の @overlast さんから今回の発表オファーを頂いたしせっかくだから何か話そう、と心に決めて、いまの会社 でのお仕事でよく取り扱っているコサイン類似度や Jaccard 係数などのいわゆる「類似度計算」の時間/空間計算量的に効率的な処理を実現する方法についてお話してきました。

自分含めた発表内容の一覧は こちら を参照してください。

発表スライドはこちらです。

発表後に頂いた質問はこんな感じだったはず。寝不足ゆえの言語認識力がだいぶ低下した状態で受け答えしたので、質問/回答は超意訳になってます。悪しからず。

  • バスケット分析的な方法の計算量的な話

    • 購買ログなどからバスケット分析的な方法で類似度計算しようとすると、ユーザごとにログを集約しなければならないという追加処理が必要となるものの、計算量的にはあんまり問題になってない。
    • むしろ問題になるのはアイテムの数。アイテム数の2乗に応じて空間計算量が必要になってしまうため、アイテム数が多い場合にはこの方法は向かない。
  • レコメンド精度の面で、Jaccard 係数とかコサイン類似度ってどうなのよ?

    • ビジネス的には、類似度計算によって得られる精度よりもドメイン知識を使ったフィルタリングを良くする方が大事だと考えてる。
    • あと弊社の事情となってしまうが、精度のよい弊社独自の類似度計算のロジックは社外秘的な扱いとなっているためしゃべれない(ので、あえて精度の話は触れなかった)。

今回の発表にあたって b-bit Min-wise hashing を Java で実装してみましたが、精度・処理性能面の特性がまだ把握しきれていないので、もうちょっと追求してみたいですね。現状の実装は以下のとおりです。

https://gist.github.com/komiya-atsushi/6414548

あと、他の方の発表の感想と今回の反省です。

  • ダブル配列の実装テクニック的な話が熱かった。Java で trie を実装するときの参考になるのでとってもためになります。

  • 浮動小数点型数値の圧縮の話、昔のとあるお仕事で FPC を組み込もうかという検討をしたことがあって、そのときのことを思い出してちょっと感慨深いものがあった。整数圧縮の部分を改善する余地がまだあるとのことなので、maropu 先生の次回作に超期待してます!

  • 僕の発表、頂いた時間内でちゃんと発表しきれたのはよかったが、聴講者の反応を見てるとあまり興味ない or 説明が不足しているゆえに理解が追いつかない状態だったっぽい。魅力的な発表テーマ&必要十分な説明を心がけたいですね。

  • 忙しいときこそ没頭してしまう「艦これ」を断つ勇気を僕にください…

10:18No comments

2013-08-21

Play! 2.x で JUnit が実行できなくて生きるのが辛い

いままでは IntelliJ IDEA 上で JUnit によるユニットテストを実行していたのですが、Play! のバージョンを 2.1.1 から 2.1.3 にマイグレーションした際に IntelliJ 上での JUnit 実行ができなくなってしまい、解決法を探るのが面倒そうだったので play test で暫定対処しようとしたものの、こちらもこちらでいろいろと地雷が埋まっていたので解消・回避方法をメモしておくことにします。

その 1 : 'play clean' してから 'play test' する

とりあえず play test してその結果が以下の表示になる場合は、 play clean をしましょう。

[warn] 1 warning
[info] No tests to run for test:test 

ここ に書かれているとおり、

You might try doing a play clean and then try play test again. I was getting the same "No tests to run" message until I did a clean.

ということで、play clean する必要があるそうです。play のコンソールを立ち上げた状態で clean タスクを実行しても思ったようにクリーンされないようなので、いったん play コンソールを抜けてから play clean するとよいです。

その 2 : junit-interface で work around する

上記の play clean をしてから play test をしても、(テストクラスは認識されるものの) それぞれのテストケースの実行結果が

[info] 0 tests, 0 failures, 0 errors

などと、個々のテストメソッドが実行されないことがあります (というかほぼ確実に実行されない?)。 そのような場合は、こちら に書かれているとおり、Build.scala に junit-interface への依存関係を追加すると問題を回避できます。

以下は Build.scala への追記例です。

  val appDependencies = Seq(
    // Add your project dependencies here,
    javaCore,
    javaJdbc,
    javaEbean,
    "play" %% "play-test" % play.core.PlayVersion.current % "test" exclude("com.novocode", "junit-interface"),
    "com.novocode" % "junit-interface" % "0.9" % "test"
  )

これでやっと play test でユニットテストが実行できるはず!

13:231 comment

2013-07-28

Play 2.x で sbt.ResolveException が発生する問題への対処

Play 2.x で開発していたアプリケーションをステージング環境で play run したり、プロダクション環境で稼動させるために play dist したときに、以下のように、sbt.ResolveException の例外が発生した旨のログ出力がされて異常終了してしまったときの対処法をメモっておきます。

[warn] some of the dependencies were not recompiled properly, so classloader is not avaialable
[info] Updating {file:/path/to/play-app/}play-app...
[warn]  [NOT FOUND  ] org.slf4j#slf4j-api;1.6.6!slf4j-api.jar (1ms)                    
[warn] ==== local: tried
[warn]   /opt/playframework/repository/local/org.slf4j/slf4j-api/1.6.6/jars/slf4j-api.jar
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  ::              FAILED DOWNLOADS            ::
[warn]  :: ^ see resolution messages for details  ^ ::
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  :: org.slf4j#slf4j-api;1.6.6!slf4j-api.jar
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
sbt.ResolveException: download failed: org.slf4j#slf4j-api;1.6.6!slf4j-api.jar
        at sbt.IvyActions$.sbt$IvyActions$$resolve(IvyActions.scala:214)
            (略)
        at java.lang.Thread.run(Thread.java:724)
[error] (*:update) sbt.ResolveException: download failed: org.slf4j#slf4j-api;1.6.6!slf4j-api.jar

原因

Play プロジェクトを構築した時の Play framework のバージョンと、play run などをしようとしたときのバージョンが異なる場合にこの例外が発生するようです。

より具体的には、Play プロジェクトが利用する sbt-plugin のバージョンは、実行環境の Play のバージョンと合わせる必要がある、ということです。

実は play run を実行したときに、実行直後のログに赤文字で

This project uses Play 2.1.1!
Update the Play sbt-plugin version to 2.1.2 (usually in project/plugins.sbt)

と出力されていたりします。どうしても例外ログの方に真っ先に目が行ってしまいがちですが、ちゃんと Play framework は警告してくれています。

対処法

project/plugins.sbt に記載されている sbt-plugin のバージョンを、実行環境の Play のバージョンと合わせましょう。例えば、Play プロジェクトを作成したときの Play のバージョンが 2.1.1 であれば、

addSbtPlugin("play" % "sbt-plugin" % "2.1.1")

となっているかと思いますが、これを、Play 2.1.2 の環境で稼動させようとするなら

addSbtPlugin("play" % "sbt-plugin" % "2.1.2")

としましょう。

19:54No comments