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が取得されない" ように見えました。具体的には以下です。

TypeProfileTest/MainWithoutFactoryMethodAndImportingB.java at master · komamitsu/TypeProfileTest · GitHub

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で扱えない色々なケースに対応しているっぽいので便利っぽい(ぽいぽい)。

本家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 · GitHubmojotech/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をチェックする方法

先日、


という事例を聞いたので何とかコンパイル時に検知出来ないかぼーっと考えてて、プリプロセス的にチェックできないか思いついたので試してみました。

#ちなみに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に組み込むか(ユーザーが特に何もせずに当該チェックが作動するか)が思いついてない...