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の結果はこれ。
Subplanを経由してのStageExecutionPlan:
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が取得されない" ように見えました。具体的には以下です。
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に突っ込んで頂けると嬉しいです。