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に突っ込んで頂けると嬉しいです。

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

Androidでのnew String(byte[])とCharsetDecoder.decode()の違い

byte[] -&gt; String 変換のパフォーマンス、とAndroidでCharsetDecoder#decode(ByteBuffer in).toString()が遅い件 - komamitsu.logAndroidではnew String(byte)の方が速い結果が出たので[https://github.com/OESF/OHA-Android-4.0.3_r1.0:title=コード]を見てみた。

すると、CharsetDecoder.decode()ではICU Library(IBMの人が作ったunicode用ライブラリ)を使っているが、new String(byte, "UTF-8")の方は自前で手抜きしつつガリガリconvertしていた。なので、new String(byte)の方が高速で低機能なのではないかと思われ。

追記:
OpenJDK6のコードを見たら、CharsetDecoderの方はICUを使わずに自前でガリガリ。new String(byte)の方はCharsetDecoder#decodeを使ってた。

byte[] -> String 変換のパフォーマンス、とAndroidでCharsetDecoder#decode(ByteBuffer in).toString()が遅い件

Android上でMessagePackのベンチマークコードを動かしつつprofile取ってみたら、msgpack-java/src/main/java/org/msgpack/unpacker/StringAccept.java at master · msgpack/msgpack-java · GitHub辺り(CharsetDecoder#decode(ByteBuffer in).toString())でほとんどの時間が掛かっているなぁ、という感じ。

で、そもそも new String(byte[] byte, String charsetName) と CharsetDecoder#decode(ByteBuffer in).toString() でどれくらい性能があるのか気になったので計測してみた。確か、new String(...) の内部で CharsetDecoder#decode() してるというのは以前調べたことがあったので、今回はどの位違ってくるのか?という点に着目。

  public static void main(String[] args) throws IOException {
    String src = "あいうえおアイウエオアイウエオaiueoAIUEO";
    byte[] raw = src.getBytes();
    ByteBuffer byteBuffer = ByteBuffer.wrap(raw);
    Charset cs = Charset.forName("UTF-8");
    int until = 10000000;

    long start = System.currentTimeMillis();
    for (int i = 0; i < until; i++) {
      cs.newDecoder().decode(byteBuffer).toString();
    }
    System.out.println("CharsetDecoder.decode(): " + (System.currentTimeMillis() - start));

    start = System.currentTimeMillis();
    for (int i = 0; i < until; i++) {
      new String(raw, "UTF-8");
    }
    System.out.println("String(byte[]): " + (System.currentTimeMillis() - start));
  }

こんなのを書いて動かして見たところ...

CharsetDecoder.decode(): 720
String(byte[]): 2367

ざっと3倍位new String(byte[] byte, String charsetName)のほうが遅かった。OpenJDK 1.6でも大体同じ。

I/XXXX    (28542): CharsetDecoder.decode(): 4366
I/XXXX    (28542): String(byte[]): 1471

同等のコードをDalvik VM上で動かしたら(ループの回数は1/100に減らした)、結果が逆転した。ちょうど三倍程度、CharsetDecoder.decodeの方が遅くなった。

この辺がmsgpack-javaAndroid上で動かすと遅くなる件と関係あるのかも。ないのかも。

ScalaのTraitをjavapしてみたメモ

Traitがどんな感じでJavaバイトコードに落ちているのか興味があったので試してみた。

komamitsu@carrot ~/lab/scala/traitsample2 $ scala -version
Scala code runner version 2.9.1.final -- Copyright 2002-2011, LAMP/EPFL
trait T1 {
  val age:Int
  val name:String
}

trait T2 {
  def hello(name:String) = println("hello " + name)
}

class C extends T1 with T2 {
  val age = 42
  val name = "komamitsu"
}

でjavapした結果がこれ

komamitsu@carrot ~/lab/scala/traitsample2 $ javap -private -c T1
Compiled from "Hello.scala"
public interface T1{
public abstract int age();

public abstract java.lang.String name();
}

komamitsu@carrot ~/lab/scala/traitsample2 $ javap -private -c T2
Compiled from "Hello.scala"
public interface T2 extends scala.ScalaObject{
public abstract void hello(java.lang.String);
}

komamitsu@carrot ~/lab/scala/traitsample2 $ javap -private -c T2\$class
Compiled from "Hello.scala"
public abstract class T2$class extends java.lang.Object{
public static void hello(T2, java.lang.String);
  Code:
   0:   getstatic       #11; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   new     #14; //class scala/collection/mutable/StringBuilder
   6:   dup
   7:   invokespecial   #18; //Method scala/collection/mutable/StringBuilder."<init>":()V
   10:  ldc     #20; //String hello 
   12:  invokevirtual   #24; //Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
   15:  aload_1
   16:  invokevirtual   #24; //Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
   19:  invokevirtual   #28; //Method scala/collection/mutable/StringBuilder.toString:()Ljava/lang/String;
   22:  invokevirtual   #32; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   25:  return

public static void $init$(T2);
  Code:
   0:   return
}

komamitsu@carrot ~/lab/scala/traitsample2 $ javap -private -c C
Compiled from "Hello.scala"
public class C extends java.lang.Object implements T1,T2,scala.ScalaObject{
private final int age;

private final java.lang.String name;

public void hello(java.lang.String);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #15; //Method T2$class.hello:(LT2;Ljava/lang/String;)V
   5:   return

public int age();
  Code:
   0:   aload_0
   1:   getfield        #23; //Field age:I
   4:   ireturn

public java.lang.String name();
  Code:
   0:   aload_0
   1:   getfield        #26; //Field name:Ljava/lang/String;
   4:   areturn

public C();
  Code:
   0:   aload_0
   1:   invokespecial   #32; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   invokestatic    #36; //Method T2$class.$init$:(LT2;)V
   8:   aload_0
   9:   bipush  42
   11:  putfield        #23; //Field age:I
   14:  aload_0
   15:  ldc     #38; //String komamitsu
   17:  putfield        #26; //Field name:Ljava/lang/String;
   20:  return
}

ということで、Java的には以下のような感じになっているみたい。

  • valなどのメンバー変数について
    • アクセサはTraitのInterface側に
    • 変数の実体は継承したClass側に作成される
  • defについて
    • TraitのInterface側に呼び出し口が作成される
    • 実装はInterfaceを継承しているClass側に作成される(この辺はJavaの普通のInterfaceの実装と同じ)
      • ただし実質的な処理はTrait InterfaceのInner Classにstatic methodが出来ているので、それを呼び出すだけ

using multiple databases with Spring iBatis

Spring経由でiBatisを使いつつ、複数のデータベースを利用しようと思ったら結構ハマったのでまとめておきます。


やりたいこと

  • database1にはpersonというtableがあって、database2にはprojectというtableがあり、それぞれにアクセスしたい。


解決方法の概要

  • database1, 2のそれぞれに対応するdataSourceを定義(dataSource1, dataSource2)
  • dataSource1,2を用いるsqlMapClient(sqlMapClient1,2)とtransactionManager(transactionManager1,2)をそれぞれ定義
  • sqlMapClient1,2内のconfigLocationは別ファイルにしておき(sqlmap-config-a.xml, sqlmap-config-b.xml)それぞれperson, projectテーブルに関するマッピングを定義
  • SqlMapClientDaoSupportを継承しているDaoで、setSqlMapClient()する際に適切なsqlMapClientが利用されるようにする。
    • たとえばアノテーションを用いてAutowireしているのであれば以下のような感じ
  @Autowired
  @Qualifier("sqlMapClient1")
  public void injectSqlMapClient(SqlMapClient sqlMapClient) {
    setSqlMapClient(sqlMapClient);
  }
    • これがかなり重要で、Spring, iBatisのバージョンが新しければそれっぽいエラーがでてくれるが、古めの場合は不適切なsqlMapClientがセットされるだけで進んでしまうので以下のようなエラーで悩むことになる
There is no statement named XXXXX in this SqlMap.
    • あとUnit testでAbstractTransactionalJUnit4SpringContextTestsを使っている場合、dataSourceとかtransactionManagerとかが期待通りにセットされないので、以下のように指定する必要があるみたい
@TransactionConfiguration(transactionManager = "transactionManager1")
public class TestSqlMapPersonDaoImpl extends AbstractTransactionalJUnit4SpringContextTests {
       :
  @Autowired
  @Qualifier("dataSource1")
  @Override
  public void setDataSource(DataSource dataSource) {
    super.setDataSource(dataSource);
  }

で、その辺を適当にまとめてサンプルっぽいものをおいておきました。
https://github.com/komamitsu/Spring-MVC-sample-using-iBatis

それにしても最近Springもまぁそんなに悪くないかなぁ、という感じになりつつありますね。分からないものだ。

Non blocking IOでecho server

まぁクリスマスなんですがJavaのNon blocking IO周りについて少し調べてました。非リアですね。

参考にしたのはうっすらと全体を網羅していたっぽいこちら => http://tutorials.jenkov.com/java-nio/index.html

で、手を動かさないと理解できない人種なので簡単にecho serverを書いてみました。

package com.komamitsu;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Main {
  private static final int PORT_NUMBER = 12345;
  private static final int BUFFER_SIZE = 256;

  public static void main(String[] args) throws IOException {
    ServerSocketChannel server = ServerSocketChannel.open();
    server.socket().bind(new InetSocketAddress(PORT_NUMBER));
    server.socket().setReuseAddress(true);
    server.configureBlocking(false);

    Selector selector = Selector.open();
    server.register(selector, SelectionKey.OP_ACCEPT);

    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
    while (true) {
      int channelCount = selector.select();
      if (channelCount > 0) {
        Set<SelectionKey> keys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = keys.iterator();
        while (iterator.hasNext()) {
          SelectionKey key = iterator.next();
          iterator.remove();

          if (key.isAcceptable()) {
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ, client.socket().getPort());
          } else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            p("port: " + key.attachment());
            if (client.read(buffer) < 0) {
              key.cancel();
              client.close();
            } else {
              buffer.flip(); // read from the buffer
              /*
               * byte[] received = new byte[buffer.remaining()];
               * buffer.get(received); buffer.clear(); // write into the buffer
               * buffer.put(received); buffer.flip(); // read from the buffer
               */
              client.write(buffer);
              buffer.clear(); // write into the buffer            }
          }
        }
      }
    }
  }

  private static void p(String s) {
    System.out.println(s);
  }
}

コツは

  • 最初にlisten channelについてacceptのevent監視を登録
  • accept eventが発生したらそのclient channelに対してreadのevent監視を登録
  • read eventが発生したらclient channelから読んで書き返す
  • ByteBufferはallocate後はwrite modeで、読む際はflip()してread modeにしてから。また書き込む際(channel.write()とか)はclear() or compact()してwrite modeにしてから
  • event setのselector.selectedKeys()は外部イテレータからremove()してあげないとeventが残ったままになっちゃう(次回のselect()でも残ってしまう)

とかでしょうか。特にByteBufferでmodeが切り替わるのは個人的には好きくないですねぇ...


娘達へのプレゼントは "どうぶつしょうぎ" を買いました。二人共はまってしまって、やや中毒状態です。ふふふ... 狙いどおり。

二歳になったばかりの息子には救急車のミニカーを買いました。本当は消防車のはしご車が良さげだったのですが、数日でハシゴを折られそうだったので落としどころとして知名度があり耐久性が高そうな救急車になりました。こちらはあまり好評ではありませんね...