Javaにおけるデータシリアライズと圧縮

18,425 views

Published on

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
18,425
On SlideShare
0
From Embeds
0
Number of Embeds
187
Actions
Shares
0
Downloads
127
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

Javaにおけるデータシリアライズと圧縮

  1. 1. 研究課題:Java におけるシリアライズとデータ圧縮 新規開発局 プログラマ 大平哲也 1
  2. 2. 1 はじめに CGM サイトなど、データが無尽蔵に増える種類のサイトを運営する際は、増え続けるデータの扱 いが重要である。一つの対応として、データを永続化(直列化、シリアライズ)する際にデータのサ イズを減らすことでデータ転送量やディスク容量の削減を行い、パフォーマンスを維持したりリソー スを有効活用する方法が考えられるが、ただ圧縮をすれば良い訳ではなく、復元時の速度や、プ ログラムからの扱いやすさも現実的な問題としては重要な事項になる。 まとめると、データのシリアライズ方式の検討は以下の観点における検討が大事になる。 ・ データの省サイズ化 ・ シリアライズ・デシリアライズの速度 ・ プログラミング側からみた構造の柔軟性、扱いやすさ そこで、Java におけるシリアライズ手法について調査した。 本論では、Java における各種シリアライズ手法を調査ならびに比較検証することにより、上記3点 をバランスよく満たす、システムに適用しやすいシリアライズ手法を見出すことを目的とする。 2 動機 2.1 Java シリアライズの遅さ Java は標準の言語機構においてオブジェクトインスタンスのシリアライズをサポートしている1という 優秀な言語ではあるが、非常に汎用的に作られているため単純なデータ永続化を果たしたい時に は冗長すぎる仕様がネックになるケースがあり、シリアライズ・デシリアライズの遅さ、ならびにシリア ライズデータの大きさが問題となる。(他手法との性能比較についての詳細は後述する) たとえば、Java RMI などの RPC 処理の際、デフォルトでは Serializable インターフェースを継承 したクラスをシリアライズ・デシリアライズしてやりとりを行うことになるが、単純なデータ構造のデータ を Java シリアライズそのまま用いて実装すると、他手法のシリアライズ形式を用いる際よりもパフォ ーマンス的に問題が発生することが考えられ、高負荷、高トラフィックなシステムであればある程こ のデメリットは大きくなる。 2.2 優秀な他手法の存在 近年は、多言語に対応可能でかつ高速・高機能なシリアライズ・デシリアライズ機構が多く世の中 に知られるようになっている。具体的には Google の Protocol Buffers2 、Hadoop の Hadoop Avro3、筑波大学の古橋貞之氏が製作した Message Pack4などである。 それぞれ手法ごとに機能的、性能的な特徴があるが、Java のシリアライズ機構との性能比較を実 際に行い、各手法のメリット・デメリットを把握したいと考えた。 3 本論の流れ まず、各種シリアライズ手法を概観する。 今回は Java の標準 API(Serializable、Externalizable)、JSON 形式へのシリアライズツール (JSONIC、FlexJson)、Protocol Buffers、Hadoop Avro、Message Pack を対象とした。 その上で、それぞれの手法におけるシリアライズ・デシリアライズのパフォーマンスを調査し、比較し た。今回はシリアライズ速度、デシリアライズ速度、シリアライズ後のデータサイズ、ならびに実装の 1 http://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3 %82%BA なお他言語では Ruby では Marshal クラス、Python では Pickling により言語機構によりシリアライズがサポー トされている。 2 http://code.google.com/intl/ja/apis/protocolbuffers/docs/overview.html 3 http://hadoop.apache.org/avro/ 4 http://sourceforge.jp/projects/msgpack/ 2
  3. 3. しやすさを評価軸とした。また、データ圧縮アルゴリズムを用いた際のパフォーマンスとデータサイ ズについても合わせて調査した。 そして最後に、調査結果を基に BtoC サイト、CGM サイトなどに適用しやすい組み合わせについ てまとめる。 4 各種シリアライズ手法 4.1 Java 標準 API:Serializable Java にて、オブジェクトインスタンスのシリアライズが必要な場合は、シリアライズ該当クラスにて java.io.Serializable1インターフェースを継承する。 これだけで、そのクラスはシリアライズ対応クラスとなる。 import java.io.Serializable; public class SerializableObject implements Serializable { private String name; //シリアライズのとき保存される。 static String staticField; //クラス変数は無視される。 private transient int hashCode; //transient キーワードが付いたフィールドは無視 される。 //シリアライズしたいクラスの実装... }2 実際のデータシリアライズ・デシリアライズを行う処理は自分で実装する必要がある。これらの処理 は Java RMI を用いる場合は Java RMI 機構が自動的に対応する。 import java.io.FileOutputStream; import java.io.OutputStream; import java.io.ObjectOutputStream; //... SerializableObject object = new SerializableObject(); OutputStream outputStream = new FileOutputStream("serializableObject.obj"); ObjectOutputStream objectOutputStream= new ObjectOutputStream(outputStream); objectOutputStream.writeObject(object);3 実際シリアライズされたデータは以下のようなものになる。以下はシリアライズデータをバイナリエデ ィタで閲覧したものである。 なお仕様については SUN の公式ドキュメントが詳しい4。 ac ed 00 05 73 72 00 0d 74 65 73 74 2e 54 65 73 1 http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/io/Serializable.html 2 http://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3 %82%BA#Serializable より引用 3 http://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3 %82%BA#.E3.83.90.E3.82.A4.E3.83.8A.E3.83.AA.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB.E3.81.AB. E3.82.B7.E3.83.AA.E3.82.A2.E3.83.A9.E3.82.A4.E3.82.BA より引用 4 http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/serialization/index.html 3
  4. 4. 74 42 65 61 6e 20 e9 e2 62 c4 df 79 42 02 00 03 4a 00 02 69 64 4c 00 04 74 69 6d 65 74 00 10 4c 6a 61 76 61 2f 75 74 69 6c 2f 44 61 74 65 3b 4c 00 05 76 61 6c 75 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 70 00 00 00 00 00 00 00 01 73 72 00 0e 6a 61 76 61 2e 75 74 69 6c 2e 44 61 74 65 68 6a 81 01 4b 59 74 19 03 00 00 78 70 77 08 00 00 01 27 74 e6 d2 3c 78 74 00 04 74 65 73 74 (....sr..test.Tes tBean ..b..yB... J..idL..timet..L java/util/Date;L ..valuet..Ljava/ lang/String;xp.. ......sr..java.u til.Datehj..KYt. ...xpw....'t..<x t..test) 先頭の「ac ed」はシリアライズ形式を示すマジックナンバー。「00 05」は Java のバージョン番号と なる。以降オブジェクトの定義や変数の定義、変数の値などが埋め込まれている。 ヘッダ情報やオブジェクトの定義の情報が含まれているため、単純にデータのみを構造化してシリ アライズする手法よりもデータサイズが冗長となる。 4.2 Java 標準 API:Externalizable Java には、シリアライズデータフォーマットを自分で定義することができる API、Externalizable1 が用意されている。これを用いると、必要最低限な情報だけシリアライズすることも可能であり、デー タサイズやシリアライズ・デシリアライズ速度の向上を果たすことも可能である。 Externalizable にてシリアライズを行いたい場合は、該当クラスにて java.io.Externalizable イン ターフェースを継承した上で、必要なメソッド(writeExternal、readExternal)を実装する必要が ある。 import java.io.Externalizable; import java.io.ObjectOutput; import java.io.ObjectInput; public class SerializableObject implements Externalizable { private String name; //writeExternal(ObjectOutput)で指定されているため保存さ れるフィールド。 private int id; //無視される。 private int hashCode; //writeExternal(ObjectOutput)で指定されているため保存さ れるフィールド。 //Externalizable で定義されているメソッドをオーバーライド。 //保存したいフィールドを指定する。 @Override public void writeExternal(ObjectOutput out) { out.writeChars(this.name); 1 http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/io/Externalizable.html 4
  5. 5. out.writeInt(this.hashCode); } //復元したいフィールドを指定する。 @Override public void readExternal(ObjectInput in) { this.name = in.readLine(); this.hasoCode = in.readInt(); } //シリアライズしたいクラスの実装 }1 シリアライズ、デシリアライズを行う処理の実装方法は、Serializable を用いた時と同様である。 4.1 で用いたクラスと同じもの(厳密にはクラス名が異なる2)を Externalizable にてシリアライズした ものが以下となる。 ac ed 00 05 73 72 00 1b 74 65 73 74 2e 45 78 74 65 72 6e 61 6c 69 7a 61 62 6c 65 54 65 73 74 42 65 61 6e a3 be c9 30 53 4a 7a f8 0c 00 00 78 70 77 08 00 00 00 00 00 00 00 01 74 00 04 74 65 73 74 73 72 00 0e 6a 61 76 61 2e 75 74 69 6c 2e 44 61 74 65 68 6a 81 01 4b 59 74 19 03 00 00 78 70 77 08 00 00 01 27 74 e6 d3 af 78 78 (....sr..test.Ext ernalizableTestB ean...0SJz....xp w.........t..tes tsr..java.util.D atehj..KYt....xp w....'t...xx) 先頭のヘッダ情報や、オブジェクトの定義については Serializable と同様に存在するが、それ以 降のデータの定義が最低限のもののみ出力されているため、Serializable に比べてデータサイズ が小さくなっている。 4.3 JSON 形式:JSONIC 構造化されたデータのテキストシリアライズ方式としては、近年は JSON3 形式が普及し、とくに BtoC サイトなどでは一般化している。 理由としては簡潔で直感的なデータ構造記法や、RFC に定義されている4など仕様の堅牢化、ライ ブラリの充実などが上げられる。 1 http://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3 %82%BA#Externalizable より引用 2 4.1 では「test.TestBean(74 65 73 74 2e 54 65 73 74 42 65 61 6e)、4.2 では 」 「test.ExternalizableTestBean (74 65 73 74 2e 45 78 74 65 72 6e 61 6c 69 7a 61 62 6c 65 54 65 73 74 42 65 61 6e) 」を使用している。 3 http://ja.wikipedia.org/wiki/JavaScript_Object_Notation 4 http://www.rfc-editor.org/rfc/rfc4627.txt 5
  6. 6. テキストシリアライズ手法としては他に XML1や YAML2などが存在するが、XML はスキーマ定義 が厳格に行える一方スキーマレスでも十分な軽いデータを扱うには冗長すぎる点がある。YAML については JSON と同様の軽さがあるが、JSON 程一般化していない。 そのため、ここでは JSON のみを対象とし、まずは JSON の Java 用シリアライズライブラリとして高 機能な JSONIC3について触れる。 JSONIC は Apache Lisence Version2.0 で提供されているオープンソースライブラリである。 シリアライズ・デシリアライズの記述が以下のように極めてシンプルに行えることが特徴である。 import net.arnx.jsonic.JSON; // POJO を JSON に変換します String text = JSON.encode(new Hoge()); // JSON を POJO に変換します Hoge hoge = JSON.decode(text, Hoge.class);4 出力形式は以下のような純粋な JSON 形式となり、クラス名などのメタ情報は含まれない。 それ故にデータ構造としては簡潔ではあるがデシリアライズ時には復元先のオブジェクトの明示的 な指定が必須になる。(シリアライズデータの情報のみから復元することはできない) {"id":1,"time":1268976636047,"value":"test"} 記述が簡便であることと高機能であることがメリットだが、データのシリアライズ等に Apache Commons の BeanUtils5を多用しており、必ずしも処理は高速では無い。 また、出力内容が JSON 形式になることを前提としているため、文字列のみや数値のみのデータ など、JSON 形式に成り得ないデータのシリアライズが出来ない仕様になっているので注意が必要 である。 4.4 JSON 形式:FlexJson FlexJson も JSONIC と同様 Apache Lisence Version 2.0 で公開されているオープンソースライ ブラリである。JSONIC と比べるとシリアライズデータにメタ情報(クラス名)が含まれ若干冗長であ ること、いくつかのバグが散見されるなどの問題もあるが、JSONIC に比べて高速にシリアライズが 行えるので紹介する。 シリアライズ・デシリアライズの記述は以下のようになる。 T obj; String json = new JSONSerializer().serialize(obj); T deserialized = new JSONDeserializer<T>().deserialize(json); クラスの一階層だけをシリアライズ(Shallow Serialize)/全てのデータをシリアライズ(Deep Serialize)したり、特定の名前空間の変数を include/exclude することができるのが FlexJson の 特徴である。また実装上もリフレクションを出来るだけ用いない工夫が見られる。 シリアライズ後のデータは以下のようになる。 1 http://ja.wikipedia.org/wiki/Extensible_Markup_Language 2 http://ja.wikipedia.org/wiki/YAML 3 http://jsonic.sourceforge.jp/ 4 http://jsonic.sourceforge.jp/ より引用 5 http://commons.apache.org/beanutils/ 6
  7. 7. {"class":"test.TestBean","id":1,"time":1268976636560,"value":"test"} 4.5 Protocol Buffers Protocol Buffers は Google が BigTable などの社内のシステムに用いているとされる、データシリ アライズフォーマットと、そのデータの RPC 通信を行うためのフレームワークである。2008 年の7月 に Apache Lisence Version 2.0 のオープンソースプロジェクトとして外部に公開された。 Protocol Buffers は IDL1と呼ばれる独自 DSL 言語を基に各種プログラム言語用のソースコード を自動生成できることが特徴である。IDL 定義文は以下のようなものである。 package serializable.protobuf; message TestBean{ required int32 id = 1; required string value = 2; required int64 time = 3; } 上記を拡張子.proto として保存し、コード生成用のコマンドを実行すると各種言語向けのデータ操 作用プログラムファイルが自動生成される。 protoc --java_out=. --cpp_out=. --python_out=. ProtobufTestBean.proto 上記は、proto ファイルを基に Java、C++、Python のソースコードを自動生成する際のコマンドで ある。 生成されるソースコードは自動生成という事もあってか非常に冗長で、上記 proto ファイルの場合 Java で 422 行、C++でヘッダとソースを合わせて 652 行、Python で 67 行となった。 データのシリアライズ・デシリアライズ処理は自動生成コードで堅牢に行われるためか、シリアライズ されるデータ形式は非常にシンプルで無駄が無いものになっている。 4.3、4.4 と同じデータ構造を持つクラスをシリアライズした結果が以下になる。 08 01 12 04 74 65 73 74 18 f6 e2 b3 e1 06 (....test......) データは非常にシンプルで省サイズ化が可能であるが、生成コードにクセがあり、既存のシステム に導入するためには Protocol Buffers 向けに実装を作り直すことが必要となる。 4.6 Hadoop Avro Hadoop Avro は、分散並列処理フレームワークとして著名な Hadoop プロジェクト2のサブプロジェ クトとして、Hadoop 創始者の Doug. Cutting 氏によって 2009 年4月に立ち上げられた3新しいプ ロジェクトである。 Hadoop における RPC 処理や、 サブプロジェクトである Hadoop Pig4、 Hive5などでのシリ アライズ形式、RPC 形式を統一する、という目的で立ち上げられたようである。 1 http://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3 %83%BC%E3%82%B9%E8%A8%98%E8%BF%B0%E8%A8%80%E8%AA%9E 2 http://hadoop.apache.org/ 3 http://markmail.org/message/7cgrwoc4er4mr3bp 4 http://hadoop.apache.org/pig/ 5 http://hadoop.apache.org/hive/ 7
  8. 8. 非常に多機能なことと、 ドキュメントが皆無であることから全体像は詳述できないが、ここ ではデータのシリアライズ方式についてのみ述べる。 Avro では、JSON 形式で記述したスキーマ定義を基にデータのシリアライズ、デシリアラ イズを行う。たとえば配列形式でシリアライズを行いたい場合は {"type": "array", "items": "string"} のような形で定義する。 なおスキーマ定義は Java クラスから自動生成することも可能である。 以下はシリアライズ時の処理である。 DataFileWriter<T> writer = null; Schema schema = null; if(schemastr != null){ schema = Schema.parse(schemastr); }else if(clazz != null){ ReflectData reflect = ReflectData.get(); schema = reflect.getSchema(clazz); } try{ writer = new DataFileWriter<T>(new ReflectDatumWriter<T>(schema)); writer.create(schema, out); writer.append(obj); writer.sync(); }finally{ if(writer != null) writer.close(); } 上記ソースでは、JSON 定義がある時は「Schema.parse」メソッドによりスキーマを生成 し、ない時は「ReflectData」インスタンスを用いて対象クラスからスキーマを生成してい る。 デシリアライズ時は以下のような処理になる。 DataFileStream<T> stream = null; Schema schema = null; if(schemastr != null){ schema = Schema.parse(schemastr); }else if(clazz != null){ ReflectData reflect = ReflectData.get(); schema = reflect.getSchema(clazz); } try{ stream = new DataFileStream<T>(is, new ReflectDatumReader<T>(schema)); return stream.next(); }finally{ if(stream != null) 8
  9. 9. stream.close(); } 上記では一つのクラスファイルを前提とした処理になっているが、 複数のクラスをまとめて シリアライズ・デシリアライズできることが Avro の一つの特徴である。 シリアライズしたデータは以下のようになる。 JSON によるスキーマ定義情報もシリアライズデータの中に含まれるため、やや冗長にな っている。ただし、シリアライズ・デシリアライズ速度は優秀である。 4f 62 6a 01 02 16 61 76 72 6f 2e 73 63 68 65 6d 61 ca 02 7b 22 74 79 70 65 22 3a 22 72 65 63 6f 72 64 22 2c 22 6e 61 6d 65 22 3a 22 50 72 69 6d 69 74 69 76 65 54 65 73 74 42 65 61 6e 22 2c 22 6e 61 6d 65 73 70 61 63 65 22 3a 22 74 65 73 74 22 2c 22 66 69 65 6c 64 73 22 3a 5b 7b 22 6e 61 6d 65 22 3a 22 69 64 22 2c 22 74 79 70 65 22 3a 22 6c 6f 6e 67 22 7d 2c 7b 22 6e 61 6d 65 22 3a 22 76 61 6c 75 65 22 2c 22 74 79 70 65 22 3a 22 73 74 72 69 6e 67 22 7d 2c 7b 22 6e 61 6d 65 22 3a 22 74 69 6d 65 22 2c 22 74 79 70 65 22 3a 22 6c 6f 6e 67 22 7d 5d 7d 00 0c 64 69 4e 57 ee 5f 83 7d a0 a8 7f b7 bb 60 33 02 18 02 08 74 65 73 74 f4 cf b6 ce ee 49 0c 64 69 4e 57 ee 5f 83 7d a0 a8 7f b7 bb 60 33 (Obj...avro.schem a..{"type":"reco rd","name":"Prim itiveTestBean"," namespace":"test ","fields":[{"na me":"id","type": "long"},{"name": "value","type":" string"},{"name" :"time","type":" long"}]}..diNW._ .}.....`3....tes t.....I.diNW._.} .....`3) 枯れたプロジェクトとは言い難いためまだまだいくつかのバグが散見されるので、 使用には 注意が必要である。 4.7 Message Pack Message Pack は古橋貞之氏が作成したシリアライズライブラリで、Apache Lisence Version 2.0 にて公開されている。 特徴としては「性能を重視したシリアライズ形式」と謳われている通り、シリアライズ・デシリアライズの 9
  10. 10. 速度が非常に優秀であることが挙げられる。また、Java を含む各種言語向けの操作用ライブラリが 提供されている。 シリアライズは以下のような形で実装する。 Packer pack = new Packer(out); if(schema == null){ pack.pack(obj); }else{ pack.packWithSchema(obj, schema); } スキーマ情報が存在しなくてもシリアライズ可能であるが、明示的にデータフォーマットを指定した い場合は独自書式のスキーマ定義を用意する必要がある。 たとえば配列を扱う場合は、以下のようにスキーマを定義する。 (array string) デシリアライズは以下のような形で実装する。 Unpacker unpk = null; if(bufferSize > 0){ unpk = new Unpacker(is, bufferSize); }else{ unpk = new Unpacker(is); } for(Object obj : unpk){ if(schema == null){ return (T)obj; }else{ return (T)schema.convert(obj); } } return null; 上記ソースでは一つのクラス前提の処理になっているが、Message Pack も Hadoop Avro と同様 に複数のクラスをまとめてシリアライズ・デシリアライズする事が可能になっている。 シリアライズデータは以下のようになる。以下は ASCII 文字を値としてもつ配列をシリアライズ化し たものである。JSON フォーマットのデータをバイナリ化したかのような、非常にシンプルな構成に なっていることが分かる。 dc 00 2f a1 30 a1 31 a1 32 a1 33 a1 34 a1 35 a1 36 a1 37 a1 38 a1 39 a1 3a a1 3b a1 3c a1 3d a1 3e a1 3f a1 40 a1 41 a1 42 a1 43 a1 44 a1 45 a1 46 a1 47 a1 48 a1 49 a1 4a a1 4b a1 4c a1 4d a1 4e a1 4f a1 50 a1 51 a1 52 a1 53 a1 54 a1 55 a1 56 a1 57 a1 58 a1 59 a1 5a a1 5b a1 5c a1 5d a1 5e (../.0.1.2.3.4.5. 6.7.8.9.:.;.<.=. 10
  11. 11. >.?.@.A.B.C.D.E. F.G.H.I.J.K.L.M. N.O.P.Q.R.S.T.U. V.W.X.Y.Z.[.¥.]. ^) こちらも枯れたプロジェクトとは言い難いため、特に Java 版のシリアライズ・デシリアラ イズ実装にはバグが多数存在するため、使用には注意が必要である。 4.8 ここまでのまとめ 上述した各種手法についてまとめる。 データ形式 スキーマ定義 複数パッケージ データの Java 以 外での使用 Serializable バイナリ 不要 不可 不可 Deserializable バイナリ 不要 不可 不可 JSONIC テキスト(JSON) 必要(クラス) 不可 可 FlexJson テキスト(JSON) 指定も可 不可 可 Protocol バイナリ 必要(IDL) 不可 可 Buffers Hadoop Avro バイナリ 必要(JSON) 可 可 Message Pack バイナリ 指定も可(独自書 可 可 式) データ形式:シリアライズ後のデータ形式 スキーマ定義:シリアライズ・デシリアライズ時にスキーマ定義が必要かどうか 複数パッケージ:複数のクラスを一度にシリアライズ・デシリアライズする事ができるか データの Java 以外での使用:Java 以外でもシリアライズデータを扱うことができるか。 5 各種シリアライズ手法による性能評価 今まで述べてきたシリアライズ手法各種について、性能評価を実施した。 シリアライズ手法  Java 標準 API  Serializable  Externalizable  JSON 形式:JSONIC  JSON 形式:FlexJson  Hadoop Avro  Message Pack  Protocol Bufferes 対象データ  文字列(Hello World)  文字列(1MB) ※ランダム  数値(int)※ランダム  数値(long)※ランダム  数値(double)※ランダム  配列(1KB の文字列 256 要素)※ランダム  List(1KB の文字列 256 要素)※ランダム 11
  12. 12.  Map(Key:256Byte の文字列/Value:int 1024 の要素)※ランダム  クラス(Serializable 対応)  クラス(Externalizable 対応) 評価軸  シリアライズ速度( s)  デシリアライズ速度( s)  シリアライズ後のデータサイズ(byte) テスト手法  上記各手法を用いてデータのシリアライズ・デシリアライズをそれぞれ1万回くりかえし、 結果の平均値を算出した  ディスクの読み書き速度が測定に影響しないよう、シリアライズしたデータはメモリ内に格 納し、メモリ内のデータをさらにデシリアライズした。 テスト環境  CPU:Core2Quad(Intel(R) Xeon(R) CPU E5540 @ 2.53GHz)  Memory:4GB なお、シリアライズ手法によっては、特定のデータ形式が扱えないものも存在するため、それらにつ いては評価対象外としている。 (数値としては「0」扱いとしている。後述の詳細結果内では「-」表記としている) たとえば Java 標準 API については、「クラス(Externalizable 対応)」のみ Externalizable クラ スとして実装したものに対して評価し、それ以外は Serializable 対応クラスにて評価を行った。 JSONIC についてはシリアライズ後 JSON 記法に成り得ないデータ形式(単純な文字列や数値) についてはシリアライズができないため未評価である。 また、Protocol Buffers については実装が特殊なため、「クラス(Serializable 対応)」のみの評価 となる。 12
  13. 13. 5.1 結果(シリアライズ) Java標準API 5.2 結果(デシリアライズ) Java標準API 13
  14. 14. 5.3 結果(データサイズ) Java標準API 5.4 ここまでのまとめ 結論から言うと、Message Pack がシリアライズ速度、デシリアライズ速度、シリアライズ後のデータ サイズ、全てにおいて最も優秀である。 Java 標準 API や Avro では、数値や文字列を Java のオブジェクトとして扱っているため、今回の 条件ではシリアライズ速度やデータサイズで損をしている部分があると思われるが、それを割り引い ても Message Pack の優秀さが際立つ結果となった。 Protocol Buffers についてはクラスのシリアライズ・デシリアライズについてのみ評価を行ったが、 データサイズの小ささは群を抜いている。複雑な構造のクラスや、インスタンスが多くデータが肥大 するようなクラスの場合は強みを発揮すると思われる。 ただし本論では実装の汎用性や簡易性などを考慮して、Message Pack が一番使い勝手の良い シリアライズ手法と判断する。 なお、上記グラフでは詳細な結果がわかりづらいため、本論末に詳細結果一覧を記載する。興味 の有る方は参照されたい。 6 圧縮アルゴリズムについて 以上、各種シリアライズ形式について述べてきたが、データの省サイズ化という観点から考えるとシ リアライズデータのデータ構造の効率化だけでは物足りない部分もある。 そこで、シリアライズしたデータをさらに圧縮アルゴリズムにより圧縮化することで、どの程度の省サ イズ化が果たせるか、検証を行った。 とはいえただ圧縮率が高いだけで圧縮・伸長速度が遅いアルゴリズム(bzip21等)では用途が制限 されてしまうため、 ・ 圧縮率はほどほど ・ 圧縮・伸長速度が速い というアルゴリズムを中心に調査した。 1 http://www.bzip.org/ 14
  15. 15. 今回は、一般的な圧縮アルゴリズムとして Deflate アルゴリズム、圧縮伸長速度が速いことで一部 注目を集めている QuickLZ アルゴリズムを評価対象として、性能比較を行った。 なお、以降はシリアライズ手法については Message Pack のみを対象として話を進める。 6.1 Deflate アルゴリズム Deflate1はフィル・カッツが開発した辞書式による圧縮アルゴリズムで、非常に多岐に渡って使用さ れている2。 Deflate では、辞書式圧縮方式として著名な LZ773と、頻出データに小さいデータを割り当てる圧 縮方式であるハフマン符号化4(動的ハフマン符号化)を組み合わせることで、高圧縮でかつ圧縮・ 伸長速度が比較的速いアルゴリズムを実現している。 また、圧縮レベル(1~9)を任意で設定できるため、圧縮率と圧縮・伸長速度のトレードオフを使用 者が調節できるようになっていることも特徴である。 6.2 QuickLZ アルゴリズム QuickLZ5は、GPL ライセンスで公開されているオープンソースライブラリである。圧縮・伸長速度 の速さにこだわったアルゴリズムで、他の同種のアルゴリズムに比べても速度が優秀だとされている (QuickLZ のサイトに掲載されているベンチマーク結果6では、他アルゴリズムよりも優秀である) QuizkLZ は、レベル指定(1~3)により圧縮速度と伸長速度のトレードオフを調節できるようになっ ている。レベル1だと圧縮速度優先(伸長速度はそれほど速くない)になり、レベル3だと伸長速度 優先となる。 7 圧縮アルゴリズムを用いたシリアライズの性能評価 Message Pack と各種圧縮アルゴリズムを用いて、シリアライズ・デシリアライズの性能評価を実施 した。 シリアライズ手法  Message Pack 圧縮手法  無圧縮  Deflate  圧縮・伸張速度優先(レベル1)  圧縮率優先(レベル9)  ハフマン符号化のみ  QuickLZ  圧縮速度優先(レベル1) 1 http://ja.wikipedia.org/wiki/Deflate 2 GZIP や ZIP などの圧縮形式や、HTTP 通信時の圧縮アルゴリズムとしても採用されている(GZIP 圧縮)。 Apache では圧縮用のモジュールとして mod_deflate が標準で搭載されている。また zlib というライブラリも用意 されており多くの Linux 系 OS には標準搭載されている。 3 http://ja.wikipedia.org/wiki/LZ77 4 http://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%95%E3%83%9E%E3%83%B3%E7%AC%A6%E5 %8F%B7 5 http://www.quicklz.com/ 6 http://www.quicklz.com/bench.html を参照のこと 15
  16. 16.  伸長速度優先(レベル3) 対象データ  文字列(Hello World)  文字列(1MB) ※ランダム  数値(int)※ランダム  数値(long)※ランダム  数値(double)※ランダム  List(1KB の文字列 256 要素)※ランダム  Map(Key:256Byte の文字列/Value:int 1024 の要素)※ランダム 評価軸  シリアライズ速度( s)  デシリアライズ速度( s)  シリアライズ後のデータサイズ(byte) テスト手法  上記各手法を用いてデータのシリアライズ・デシリアライズをそれぞれ1万回くりかえし、 結果の平均値を算出した  ディスクの読み書き速度が測定に影響しないよう、シリアライズしたデータはメモリ内に格 納し、メモリ内のデータをさらにデシリアライズした。 テスト環境  CPU:Core2Quad(Intel(R) Xeon(R) CPU E5540 @ 2.53GHz)  Memory:4GB 7.1 結果(シリアライズ) 16
  17. 17. 7.2 結果(デシリアライズ) 7.3 結果(データサイズ) 7.4 ここまでのまとめ QuickLZ による圧縮を試みたが、無圧縮時に比べてもデータサイズの増減がなく、圧縮が行えて なかった。QuickLZ では ASCII 文字の並びのデータについては圧縮が行われない傾向があり、 今回のような結果となっている。QuickLZ の圧縮率については次項で改めて評価する。 圧縮伸張の速度については、圧縮優先のレベル1の方が圧縮速度は速く、伸長優先のレベル3で は逆の結果が現れている。こちらについては想定通りの結果であった。 17
  18. 18. Deflate アルゴリズムについては、レベル1とレベル9、ハフマン符号化の間で大きな差が確認でき なかった。こちらも今回使用したデータの特性も影響していると考えられる。 なお、単純な数値データなど、サイズが小さいデータについては圧縮を行うと逆にデータサイズが 肥大化するため、数値等のデータについては圧縮を行うのは適切では無い。 また無圧縮の時に比べてシリアライズ・デシリアライズにおおよそ 10 倍くらい時間がかかるため、圧 縮効果とのトレードオフで採用を検討する必要がある。 なお、上記グラフでは詳細な結果がわかりづらいため、本論末に詳細結果一覧を記載する。興味 の有る方は参照されたい。 8 実データへの応用 実際のデータに対してシリアライズ+圧縮アルゴリズムを適用して、どの程度のデータ圧縮が実現 できるか、調査した。 8.1 Twitter のクロールデータ 別件で定期的にクローリングしている Twitter のつぶやきデータを用いて検証を行った。 当該データは、Twitter の Streaming API(sample API)1から取得できるデータを定期的に獲得 し、MySQL に保存しているものである。 MySQL 上では擬似的に Key-Value Store のような形でデータを保存しており、Key は文字列 (varchar(255))、Value は JSON 形式の文字列(BLOB)として保存されている。 つぶやきデータは JSON 形式に変換された上で Value として保存されており、今回圧縮対象とし たのもこの JSON のデータになる。 8.1.1 データ圧縮 データ件数  約 180 万件(1,798,947 件) データサイズ  約 750MB(751,423,568byte MySQL の MYD ファイルのみのサイズ)  1レコードあたり平均長:417byte 圧縮対象  つぶやきデータ(JSON 形式)  JSON Map 形式に変換した上で、シリアライズ(+圧縮) シリアライズ形式  Message Pack 圧縮アルゴリズム  無圧縮  Deflate(ハフマン符号化)  QuickLZ(レベル1:圧縮速度優先) 検証方法  既存のデータを dump し、1レコードずつシリアライズ+圧縮処理を行った上で別のテー ブルに保存する。  既存のデータとシリアライズデータとのデータサイズ比較は、MySQL の MYD ファイルの サイズ比較にて実施。 結果は以下のようになった。 1 http://apiwiki.twitter.com/Streaming-API-Documentation 18
  19. 19. データサイズ 元サイズ +Deflate +QuickLZ 元データを Message Pack 形式に変更することで、おおよそ 3/4 のサイズとなっている。 Message Pack+Deflate では、元のデータに比べて、おおよそ 2/3 のサイズとなっている。 JSON から Message Pack 形式に変更するだけでデータサイズをコンパクトにすることができるが、 さらにハフマン符号化などを行うことでデータの圧縮が可能であることが確認できた。 Message Pack をさらに QuickLZ で圧縮することでも更なる圧縮が行われたが、データの圧縮率 は非常に低かった(560.4MB 560.2MB)。Message Pack によりすでに効率的なデータ格納が 果たされているため、非冗長のデータに対する圧縮をスキップする傾向の強い QuickLZ での圧縮 はそれほど効果が無かったようである。 8.1.2 デシリアライズ速度 上記シリアライズデータがどの程度の速度で復元できたか、調査した。 データ件数  約 180 万件(1,798,947 件) データサイズ  無圧縮  約 560MB(560,456,384 byte MySQL の MYD ファイルのみのサイズ)  1レコードあたり平均超:311byte  Deflate  約 500MB(499,939,996 byte MySQL の MYD ファイルのみのサイズ)  1レコードあたり平均長:277byte  QuickLZ  約 578MB(560,261,688 byte MySQL の MYD ファイルのみのサイズ)  1レコードあたり平均長:311byte デシリアライズ形式  Message Pack 圧縮アルゴリズム  無圧縮  Deflate(ハフマン符号化)  QuickLZ(レベル1:圧縮速度優先) 検証方法  シリアライズデータを全件 dump し、1レコードずつ伸長処理+デシリアライズを行う。  1レコードあたりの復元速度の平均を測定。 結果としては以下のようになった。 デシリアライズ速度( s) deserialize dytes / micro sec Message Pack 25 s 12.44 byte/ s Message Pack+Deflate 31 s 8.93 byte/ s 19
  20. 20. Message Pack+QuickLZ 19 s 16.36 byte/ s Message Pack 単体、Message Pack+Deflate、Message Pack+QuickLZ いずれも1レコード あたり数十 s でのデシリアライズが行えた。非常に高負荷・高トラフィックのシステムでは微妙な数 値かもしれないが、たいていのシステムであれば許容範囲の数値と思われる。 上記のように、既存のシリアライズ形式と圧縮アルゴリズムを組み合わせることで、現実的な処理時 間でデータの圧縮が行えることが確認できた。 また、Deflate に比べて QuickLZ は圧縮率が低いが伸長速度は速い、という、仕様として言われ ている通りの結果が確認できた。 適応する現場の要件に応じて採用するアルゴリズムを変える、というのが落とし所になると考えられ る。Deflate アルゴリズムの圧縮・伸長速度で十分なのであれば当該アルゴリズムにてデータ圧縮 率を稼ぎ、何よりも速度重視の場合は Message Pack 単体で用いるか、圧縮が行われやすいデー タ形式であれば QuickLZ を用いる、というケースが考えられる。 9 まとめと考察 Java の各種シリアライズ手法について調査した。調査の結果、Message Pack が他手法に比して 速度、データサイズの点で優秀であり、かつ実装も比較的容易であることが分かった。 データ圧縮手法について Deflate と QuickLZ を調査した。Deflate は QuickLZ に対して圧縮率 で優位であり、対して QuickLZ は圧縮・伸長速度で優位であることが確認できた。 応用として、Message Pack と Deflate、QuickLZ を組み合わせて Twitter のクロールデータの圧 縮を試みた。結果、それぞれ元データの 2/3~3/4 のサイズにデータが圧縮でき、伸長速度も数十 s で、多くのシステムの場に現実的に適用可能であることが分かった。 今回の調査によりデータシリアライズ+圧縮のメリットの多さ・デメリットの少なさについて十分な裏 付けが得られたため、実際にいくつかのシステムに本手法を適用することを考えている。結果として 今まで以上にマシンリソースの有効活用が果たされることを期待している。 10 参考文献・URL [1] オブジェクト直列化(Java) http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/serialization/index.html [2] JSONIC http://jsonic.sourceforge.jp/ [3] FlexJson http://flexjson.sourceforge.net/ [4] Protocol Buffers http://code.google.com/intl/ja/apis/protocolbuffers/docs/overview.html [5] Hadoop Avro http://hadoop.apache.org/avro/ [6] Message Pack http://sourceforge.jp/projects/msgpack/ [7] MessagePack for Java 作りかけリリース! - 古橋貞之の日記 http://d.hatena.ne.jp/viver/20100115/p1 [8] Protocol Bufferes は遅い - 古橋貞之の日記 http://d.hatena.ne.jp/viver/20081116/p1 [9] QuickLZ 20
  21. 21. http://www.quicklz.com/ [10] ひけらかし会:辞書式圧縮 http://www.slideshare.net/moaikids/ss-2638826 21
  22. 22. 11 [詳細]各種シリアライズ手法による性能評価 11.1 文字列(Hello World) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 13 12 19 JSONIC - - - FlexJson 15 108 14 101 76 74 9 15 13 - - - 11.2 文字列(1MB) Java標準API シリアライズ デシリアライズ 22
  23. 23. Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 8047 1048589 JSONIC - - - FlexJson 13611 20593 1048578 4281 2166 1048644 3235 720 1048581 - - - 11.3 数値(int) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 55 81 JSONIC - - - 23
  24. 24. FlexJson 15 98 9 89 65 62 5 17 5 - - - 11.4 数値(long) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 55 82 JSONIC - - - FlexJson 17 122 19 95 68 68 5 17 9 - - - 24
  25. 25. 11.5 数値(double) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 53 84 JSONIC - - - FlexJson 20 125 18 99 73 69 5 17 9 - - - 11.6 配列(1KB の文字列 256 要素) Java標準API シリアライズ デシリアライズ 25
  26. 26. Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 1107 1520 265260 JSONIC 16206 4751 265217 FlexJson 3409 4575 265217 1337 904 264283 - - - - - - 11.7 List(1KB の文字列 256 要素) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 1236 1663 265274 26
  27. 27. JSONIC 16016 4676 265217 FlexJson 3159 5190 265217 1355 933 264283 882 640 265219 - - - 11.8 Map(Key:256Byte の文字列/Value:int 1024 の要素) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 1704 2354 275605 JSONIC 18224 5611 276463 FlexJson 1859 6505 276463 1249 1248 269334 1031 1480 270338 - - - 27
  28. 28. 11.9 クラス(Serializable 対応) Java標準API シリアライズ デシリアライズ Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 24 47 116 JSONIC 52 79 43 FlexJson 38 154 92 108 130 224 - - - 25 13 19 11.10 クラス(Externalizable 対応) Java標準API シリアライズ デシリアライズ 28
  29. 29. Java標準API データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) Java 標準 API 22 43 99 JSONIC 42 71 43 FlexJson - - - 109 133 238 - - - - - - 12 [詳細] 圧縮アルゴリズムを用いたシリアライズの性能評価 12.1 文字列(Hello World) シリアライズ デシリアライズ データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 29
  30. 30. 無圧縮 9 15 13 deflate(1) 82 965 21 deflate(9) 82 984 21 deflate(huffman) 83 1137 21 quicklz(1) 276 1125 26 quicklz(3) 255 972 26 12.2 文字列(1MB) シリアライズ デシリアライズ データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 無圧縮 3235 720 1048581 deflate(1) 77276 2092 788583 deflate(9) 78841 2146 788583 deflate(huffman) 79523 2115 788583 quicklz(1) 28580 4384 1048590 quicklz(3) 79086 4099 1048590 30
  31. 31. 12.3 数値(int) シリアライズ デシリアライズ データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 無圧縮 5 17 5 deflate(1) 78 1052 13 deflate(9) 73 969 13 deflate(huffman) 74 961 13 quicklz(1) 265 1112 17 quicklz(3) 249 1000 18 12.4 数値(long) シリアライズ デシリアライズ 31
  32. 32. データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 無圧縮 5 17 9 deflate(1) 75 989 17 deflate(9) 75 976 17 deflate(huffman) 76 957 17 quicklz(1) 260 1098 22 quicklz(3) 248 980 22 12.5 数値(double) シリアライズ デシリアライズ データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 無圧縮 5 17 9 deflate(1) 75 989 17 32
  33. 33. deflate(9) 75 976 17 deflate(huffman) 76 957 17 quicklz(1) 260 1098 22 quicklz(3) 248 980 22 12.6 List(1KB の文字列 256 要素) シリアライズ デシリアライズ データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 無圧縮 882 640 265219 deflate(1) 22358 7556 199361 deflate(9) 20893 7338 199362 deflate(huffman) 22305 7629 199362 quicklz(1) 7466 2233 265228 quicklz(3) 17957 2148 265228 33
  34. 34. 12.7 Map(Key:256Byte の文字列/Value:int 1024 の要素) シリアライズ デシリアライズ データサイズ シリアライズ( s) デシリアライズ( s) データサイズ(byte) 無圧縮 1031 1480 270338 deflate(1) 22124 8172 207454 deflate(9) 22199 8108 207455 deflate(huffman) 23816 8657 207454 quicklz(1) 7845 3108 270347 quicklz(3) 18737 2918 270347 34

×