PrestoのPlan

Prestoのコードを眺めていて、com.facebook.presto.execution.SqlQueryExecution#doAnalyzeQuery内で生成されるPlanを見てみたかったのでPrestoのJVMをアタッチしてPlanクラスの中を覗いてみた。

対象のクエリはこちら(catalogはtpch)。

select c.nationkey, count(1) from orders o 
join customer c on o.custkey = c.custkey
where o.orderpriority = '1-URGENT' group by c.nationkey

Planの結果はこれ。
f:id:komamitsu:20150405233413p:plain

Subplanを経由してのStageExecutionPlan:
f:id:komamitsu:20150426235803p:plain

MessagePack v07とJacksonの文字列を対象とした性能比較

        int cnt = 800000;

        {
            ObjectMapper mapper = new ObjectMapper();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            List<String> strList = new ArrayList<String>(cnt);
            for (int i = 0; i < cnt; i++) {
                strList.add("01234567");
            }

            System.gc();
            Thread.sleep(3000);

            long start = System.currentTimeMillis();
            mapper.writeValue(outputStream, strList);
            System.out.println("Jackson(Serialize, String): " + (System.currentTimeMillis() - start) + "ms");

            System.gc();
            Thread.sleep(3000);

            start = System.currentTimeMillis();
            mapper.readValue(outputStream.toByteArray(), new TypeReference<List<String>>() {});
            System.out.println("Jackson(Deserialize, String): " + (System.currentTimeMillis() - start) + "ms");
        }

        {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            MessagePacker packer = org.msgpack.core.MessagePack.newDefaultPacker(outputStream);

            System.gc();
            Thread.sleep(3000);

            long start = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++) {
                packer.packString("01234567");
            }
            packer.flush();

            System.out.println("MessagePack(Serialize, String): " + (System.currentTimeMillis() - start) + "ms");

            MessageUnpacker unpacker = org.msgpack.core.MessagePack.newDefaultUnpacker(outputStream.toByteArray());

            System.gc();
            Thread.sleep(3000);

            start = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++) {
                unpacker.unpackString();
            }
            System.out.println("MessagePack(Deserialize, String): " + (System.currentTimeMillis() - start) + "ms");
        }

int cnt = 1000000;

Jackson(Serialize, String): 71ms
Jackson(Deserialize, String): 60ms
MessagePack(Serialize, String): 96ms
MessagePack(Deserialize, String): 82ms

int cnt = 1000000;

Jackson(Serialize, String): 103ms
Jackson(Deserialize, String): 246ms
MessagePack(Serialize, String): 192ms
MessagePack(Deserialize, String): 158ms

int cnt = 10000000;

Jackson(Serialize, String): 574ms
Jackson(Deserialize, String): 7259ms
MessagePack(Serialize, String): 1316ms
MessagePack(Deserialize, String): 1049ms

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