PostgreSQLのWITH RECURSIVE練習でフィボナッチ数列
PostgreSQLにはWITH RECURSIVE句というものがあって、これを使うと再帰的な問い合わせが可能らしい。で、一度も使ったことがなかったのでちょっと試しにフィボナッチ数列を生成してみた。
with recursive r(a, b) as ( select 0::int, 1::int union all select b, a + b from r where b < 1000 ) select a from r; a ----- 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 (17 rows)
RubyのEnumerable#injectやOCamlのList.fold_leftみたいに最初のSELECT文で初期値を生成、UNION ALL(またはUNION)の後ろに、accumulatorを出力するSELECT文を書けば良さげ(で、ここに終了条件も含む)。
ByteBufferへのコピー速度比較
JavaでByteBufferへのコピーを行う際の速度を簡単に比較してみました。java.nio.ByteBuffer#put(byte)で1byteずつ書き込んでいくのでなければ大体同じですね。
Heap上のbyte arrayをコピー
java.nio.ByteBuffer#put(byte[], int, int)
byte[] bs = new byte[256 * 1024 * 1024]; ByteBuffer src = ByteBuffer.wrap(bs); ByteBuffer dst = ByteBuffer.allocate(bs.length); long start = System.currentTimeMillis(); dst.put(src.array(), 0, src.remaining()); System.out.println("heap.byte_array: ByteBuffer.put(byte[], int, int): " + (System.currentTimeMillis() - start));
=> heap.byte_array: ByteBuffer.put(byte[], int, int): 73
java.nio.ByteBuffer#put(java.nio.ByteBuffer)
dst.put(src);
System.out.println("heap.byte_array: ByteBuffer.put(java.nio.ByteBuffer): " + (System.currentTimeMillis() - start));
=> heap.byte_array: ByteBuffer.put(java.nio.ByteBuffer): 69
java.nio.ByteBuffer#put(byte) with loop
for (int i = 0; i < src.remaining(); i++) dst.put(src.get()); System.out.println("heap.byte_array: ByteBuffer.put(byte): " + (System.currentTimeMillis() - start));
=> heap.byte_array: ByteBuffer.put(byte): 380
Heap上のByteBufferをコピー
java.nio.ByteBuffer#put(byte[], int, int)
ByteBuffer src = ByteBuffer.allocate(256 * 1024 * 1024); ByteBuffer dst = ByteBuffer.allocate(256 * 1024 * 1024); long start = System.currentTimeMillis(); dst.put(src.array(), 0, src.remaining()); System.out.println("heap.bb: ByteBuffer.put(byte[], int, int): " + (System.currentTimeMillis() - start));
=> heap.bb: ByteBuffer.put(byte[], int, int): 72
java.nio.ByteBuffer#put(java.nio.ByteBuffer)
dst.put(src);
System.out.println("heap.bb: ByteBuffer.put(java.nio.ByteBuffer): " + (System.currentTimeMillis() - start));
=> heap.bb: ByteBuffer.put(java.nio.ByteBuffer): 70
java.nio.ByteBuffer#put(byte) with loop
for (int i = 0; i < src.remaining(); i++) dst.put(src.get()); System.out.println("heap.bb: ByteBuffer.put(byte): " + (System.currentTimeMillis() - start));
Off heap上のByteBufferをコピー
java.nio.ByteBuffer#put(byte[], int, int)
ByteBuffer src = ByteBuffer.allocateDirect(256 * 1024 * 1024); ByteBuffer dst = ByteBuffer.allocate(256 * 1024 * 1024); long start = System.currentTimeMillis(); dst.put(src.array(), 0, src.remaining()); System.out.println("off-heap.bb: ByteBuffer.put(byte[], int, int): " + (System.currentTimeMillis() - start));
=> java.lang.UnsupportedOperationException (it doesn't have array inside)
java.nio.ByteBuffer#put(java.nio.ByteBuffer)
dst.put(src);
System.out.println("off-heap.bb: ByteBuffer.put(java.nio.ByteBuffer): " + (System.currentTimeMillis() - start));
=> off-heap.bb: ByteBuffer.put(java.nio.ByteBuffer): 72
java.nio.ByteBuffer#put(byte) with loop
for (int i = 0; i < src.remaining(); i++) dst.put(src.get()); System.out.println("off-heap.bb: ByteBuffer.put(byte): " + (System.currentTimeMillis() - start));
=> off-heap.bb: ByteBuffer.put(byte): 358
TypeProfileが取得される場合、所得されない場合について
JavaではJITコンパイルに関連するTypeProfileという機構がありますが、これがどのような状況で取得されるのかちょっと興味があったので簡単に試してみました。
komamitsu/TypeProfileTest · GitHub
Childというinterfaceを継承するChildImplA/Bという二つのclassがあり、MainXxxxというclassからChild/ChildImplA/Bの扱い方を微妙に変えて、TypeProfileが取得される挙動を見てみました。
https://github.com/komamitsu/TypeProfileTest/tree/master/org/komamitsu/tptest にMainXxxx.java群が置いてありますが概要から言うと、"あるメソッド内で、あるオブジェクトが `new` によって生成されて、そのオブジェクトが当該メソッド内のlocal変数経由で呼ばれる場合のみTypeProfileが取得されない" ように見えました。具体的には以下です。
package org.komamitsu.tptest; import org.komamitsu.tptest.child.Child; import org.komamitsu.tptest.child.ChildImplA; import org.komamitsu.tptest.child.ChildImplB; public class MainWithoutFactoryMethodAndImportingB { public static void main(String argv[]) { Child c = new ChildImplA(); for (int i = 0; i < 10000000; i++) { c.exec(); } System.out.println(c.getCount()); } }
TypeProfileTest/WithoutFactoryMethodAndImportingB at master · komamitsu/TypeProfileTest · GitHub
$ java -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining org.komamitsu.tptest.MainWithoutFactoryMethodAndImportingB 62 1 b java.lang.String::hashCode (55 bytes) 72 2 b java.lang.String::indexOf (70 bytes) @ 66 java.lang.String::indexOfSupplementary (71 bytes) too big 82 3 b org.komamitsu.tptest.child.ChildImplA::exec (11 bytes) 83 4 % b org.komamitsu.tptest.MainWithoutFactoryMethodAndImportingB::main @ 10 (41 bytes) @ 17 org.komamitsu.tptest.child.ChildImplA::exec (11 bytes) inline (hot) 85 4 % org.komamitsu.tptest.MainWithoutFactoryMethodAndImportingB::main @ -2 (41 bytes) made not entrant 10000000
もうちょっと色々なケースで試してみたけれど、ひとまずはここまでで。
JavaのEscape AnalysisでNoEscapeがGlobalEscapeになってしまうケースの検証
Java ™ HotSpot Virtual Machine Performance Enhancements
Escape analysis is supported and enabled by default in Java SE 6u23 and later.
ということで最近のJVMでは有効になっているEscape analysisですが、こんな記事を見つけました。
Richard Burnison - Escape Analysis & Stack Allocated Objects
NoEscapeについて説明しているくだりで
Important to note, however, is that objects requiring finalizer execution are considered GlobalEscape and will not be stack-bound.
という記述があり、ちょっと気になったので試してみました。
$ java -version java version "1.7.0_45" Java(TM) SE Runtime Environment (build 1.7.0_45-b18) Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
まずは普通に。
public class Main { void run() { int totalLen = 0; for (int i = 0; i < 100000000; i++) { String s = new String("hello"); totalLen += s.length(); } System.out.println(totalLen); } public static void main(String argv[]) { Main main = new Main(); main.run(); } }
- Escape analysis無し
$ java -Xms512M -Xmx512M -XX:+PrintGCDetails -verbose:gc -XX:-DoEscapeAnalysis Main [GC [PSYoungGen: 132096K->432K(153600K)] 132096K->440K(503296K), 0.0030680 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 50000000 Heap PSYoungGen total 153600K, used 111393K [0x00000007f5500000, 0x0000000800000000, 0x0000000800000000) eden space 132096K, 84% used [0x00000007f5500000,0x00000007fc15c698,0x00000007fd600000) from space 21504K, 2% used [0x00000007fd600000,0x00000007fd66c010,0x00000007feb00000) to space 21504K, 0% used [0x00000007feb00000,0x00000007feb00000,0x0000000800000000) ParOldGen total 349696K, used 8K [0x00000007dff80000, 0x00000007f5500000, 0x00000007f5500000) object space 349696K, 0% used [0x00000007dff80000,0x00000007dff82000,0x00000007f5500000) PSPermGen total 21504K, used 2554K [0x00000007dad80000, 0x00000007dc280000, 0x00000007dff80000) object space 21504K, 11% used [0x00000007dad80000,0x00000007daffebf8,0x00000007dc280000)
- Escape analysis有り
$ java -Xms512M -Xmx512M -XX:+PrintGCDetails -verbose:gc -XX:+DoEscapeAnalysis Main 50000000 Heap PSYoungGen total 153600K, used 5284K [0x00000007f5500000, 0x0000000800000000, 0x0000000800000000) eden space 132096K, 4% used [0x00000007f5500000,0x00000007f5a29028,0x00000007fd600000) from space 21504K, 0% used [0x00000007feb00000,0x00000007feb00000,0x0000000800000000) to space 21504K, 0% used [0x00000007fd600000,0x00000007fd600000,0x00000007feb00000) ParOldGen total 349696K, used 0K [0x00000007dff80000, 0x00000007f5500000, 0x00000007f5500000) object space 349696K, 0% used [0x00000007dff80000,0x00000007dff80000,0x00000007f5500000) PSPermGen total 21504K, used 2552K [0x00000007dad80000, 0x00000007dc280000, 0x00000007dff80000) object space 21504K, 11% used [0x00000007dad80000,0x00000007daffe060,0x00000007dc280000)
Escape analysis有りの場合、Eden領域の使用量が少なくHeapではなくStack allocationになっているものと推測。
では、finalize() 実装版
public class Main { static int finalizeCount = 0; void run() { int totalLen = 0; for (int i = 0; i < 10000000; i++) { String s = new String("hello"); totalLen += s.length(); } System.out.println(totalLen); System.out.println(finalizeCount); } @Override protected void finalize() throws Throwable { finalizeCount++; super.finalize(); } public static void main(String argv[]) { Main main = new Main(); main.run(); } }
これをEA有効にして実行してみると
$ java -Xms512M -Xmx512M -XX:+PrintGCDetails -verbose:gc -XX:+DoEscapeAnalysis Main 50000000 0 Heap PSYoungGen total 153600K, used 5284K [0x00000007f5500000, 0x0000000800000000, 0x0000000800000000) eden space 132096K, 4% used [0x00000007f5500000,0x00000007f5a29028,0x00000007fd600000) from space 21504K, 0% used [0x00000007feb00000,0x00000007feb00000,0x0000000800000000) to space 21504K, 0% used [0x00000007fd600000,0x00000007fd600000,0x00000007feb00000) ParOldGen total 349696K, used 0K [0x00000007dff80000, 0x00000007f5500000, 0x00000007f5500000) object space 349696K, 0% used [0x00000007dff80000,0x00000007dff80000,0x00000007f5500000) PSPermGen total 21504K, used 2552K [0x00000007dad80000, 0x00000007dc280000, 0x00000007dff80000) object space 21504K, 11% used [0x00000007dad80000,0x00000007daffe2f0,0x00000007dc280000)
と、finalize() 実装前と変わらないので、"objects requiring finalizer execution are considered GlobalEscape and will not be stack-bound" というのは(少なくとも1.7.0_45では)気にしなくて良さそうかなぁと思います。
jackson-dataformat-msgpack
数ヶ月前から
というのを細々と作ってました。
これは何かというと、
msgpack/msgpack-java at v07-develop · GitHub で開発が進められているMessagePack Javaのv0.7系の実装と
FasterXML/jackson-databind · GitHub の豊富なデータバインディング機能を連携させるライブラリです。
作った背景ですが、
- 性能面およびメンテナンス性において大きく改善すべく開発が進められているmsgpack-java v07では、v06まで提供していたある程度のデータバインディング機能を自前で提供する予定が今のところ無いので何かしら必要
- そもそものmsgpack-java v06までのデータバインディング機能の機能的な制限が結構辛い
といった感じです。
現状でもv06で扱えない色々なケースに対応しているっぽいので便利っぽい(ぽいぽい)。
- Nested maps and arrays supported? · Issue #131 · msgpack/msgpack-java · GitHub
- Working with enums · Issue #135 · msgpack/msgpack-java · GitHub
- Cross-platform Objects · Issue #136 · msgpack/msgpack-java · GitHub
本家jackson-databind (JSON format)と比較すると(
https://github.com/komamitsu/jackson-dataformat-msgpack/tree/master/src/test/java/org/msgpack/jackson/dataformat/msgpack/benchmark
)性能的には下記のような感じで 1.1 ~ 1.5 倍くらい遅いです。とりあえず手なりで書いて、msgpack-java v07自体で改善した方が良さそうなところを直してもらって、位しかまだやってないので、そろそろjackson-dataformat-msgpack自体の性能を改善する時期なのかもしれないです。
normal object mapper: deserialize(huge_data) => 354 msgpack object mapper: deserialize(huge_data) => 371 normal object mapper: serialize(huge_data) => 130 msgpack object mapper: serialize(huge_data) => 137 normal object mapper: serialize(pojo) => 1288 msgpack object mapper: serialize(pojo) => 1594 normal object mapper: deserialize(pojo) => 2075 msgpack object mapper: deserialize(pojo) => 2998
あと、msgpack-java v07は現状Android (Dalvik VM) では動かないので、結果的に jackson-dataformat-msgpack でも動かない状態なので、後で msgpack-java v07 自体のほうも対応しないとなぁとか思ってます。
とりあえず、割と使い勝手が良い感じがするので使って頂いて、フィードバック等あればissueに突っ込んで頂けると嬉しいです。
chef-client実行時のエラーをHipChatに通知する方法
TreasureDataではエンジニア間のコミュニケーションツールとしてHipChatをがっつり使っているのですが、同じくTDで使われているchefのchef-client実行時のエラーをHipChatの部屋に流すようにしてみました。という小ネタ。
Chefには About Handlers — Chef Docs という機構があって、chef-clientの起動、成功・失敗時に発動する処理を登録できます。特にexception handlerはchef-clientが正常に処理できなかった場合に何かしらの通知をする、といった使い方ができるので有用かと思います。
Chef::Handlerを継承して自作のHandlerを作ればぶっちゃけ何でもできてしまうのですが、今回はできるだけコードを書かずに目的を達成してみます。自分で色々なHandlerを作ってみたい場合は前述のリンクを一読してみることをお勧めします。
まずはじめに、cwjohnston/chef-hipchat · GitHub にHipChat用のcookbookがあるので、これを利用可能にしておきます。
この cwjohnston/chef-hipchat · GitHub は mojotech/hipchat · GitHub で提供されているHipChat用Handlerを、error_handlersに簡単に登録する実装を持っているので、これを有効にします。これによってchef-clientの失敗時にHipChatの任意のroomに通知が飛ぶようになります。対象となるchefのroleに以下のような変更を加えることで、この機能が有効になるかと思います。
run_list( : "recipe[hipchat::handler]", : ) override_attributes( : :hipchat => { 'handler' => { 'token' => '123a456b789c098d765e432f', 'room' => 'ChefAlert', 'enabled' => true, } },
ここで、'token' はHipChatのAPI Token, 'room' はHipChatの通知先を指定してください。
chef-clientの失敗をHipChat等に通知させるようにしておくと、たまに意外なエラーが発生しているのがわかって興味深かったり、致命的な問題を未然に防ぎやすくなるのでおすすめです。
コンパイル時に特定のAnnotationが付いているClassのmodifierをチェックする方法
先日、
@ Messageのついたunpack対象のclassの定義に public staticとつけていないのが原因だった。
— Kumazaki Hiroki (@kumagi) 2014, 2月 28
@komamitsu_tw これ、コンパイル時に怒らせる事ってできないんでしょうか。できないんですよねリフレクションだから…
— Kumazaki Hiroki (@kumagi) 2014, 2月 28
という事例を聞いたので何とかコンパイル時に検知出来ないかぼーっと考えてて、プリプロセス的にチェックできないか思いついたので試してみました。
#ちなみにTweet上ではリフレクションとあったけど、多分実際はJavassistを使ってる気がする
今回サンプルとして動かしてみたのは、@Helloというannotationがついたクラスはstaticじゃないとエラーにするやつ。
org.komamitsu.pptest.annotation.Hello
package org.komamitsu.pptest.annotation; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) public @interface Hello {}
org.komamitsu.pptest.Main
package org.komamitsu.pptest; import org.komamitsu.pptest.annotation.Hello; public class Main { @Hello static class A { } @Hello class B { } public static void main(String argv[]) { System.out.println("helloworld"); } }
こんな感じになっている場合に、コンパイル時にclass Bをエラーとしたい。
で、プリプロセス的なタイミングでチェックするやつがこれ
org.komamitsu.pptest.preprocessor.Hello
package org.komamitsu.pptest.preprocessor; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.annotation.processing.*; import javax.tools.Diagnostic.*; @SupportedAnnotationTypes(value= {"*"}) public class Hello extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element elm : roundEnv.getElementsAnnotatedWith(org.komamitsu.pptest.annotation.Hello.class)) { Set<Modifier> modifiers = elm.getModifiers(); if (!modifiers.contains(Modifier.STATIC)) { processingEnv.getMessager().printMessage(Kind.ERROR, "@Hello: Only accepts static class. " + elm); } } return true; } }
一応、これも。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.komamitsu</groupId> <artifactId>pptest</artifactId> <version>0.0.1-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
Main classとかをコンパイルするときにorg.komamitsu.pptest.preprocessor.Helloを挟んであげれば良さそうなのだけど、スマートな方法が思いつかず以下のような手順で...
$ pwd /Users/komamitsu/Dev/workspace/pptest $ mvn clean package : $ cd src/main/java/ $ javac -cp ../../../target/pptest-0.0.1-SNAPSHOT.jar -processor error: @Hello: Only accepts static class. org.komamitsu.pptest.Main.B 1 error
本当はちょこっとwarningが出てるんだけど、見辛いし本筋と関係ないので省略。
とまあ、こんな感じでコンパイル時にチェックできそうなんだけども、これをどうmsgpackに組み込むか(ユーザーが特に何もせずに当該チェックが作動するか)が思いついてない...