MADlib には、sparse(疎)な配列、つまり多くの要素がゼロであるような配列を扱うデータ型として svec というデータ型があります。
- MADlib: Sparse Vectors
https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html
■「MADlib」とは何か
MADlib については、ガッツリと割愛します。以前のエントリで詳しくご紹介しましたので、そちらを参照してください。
- データ分析用ライブラリ MADlib を使って PostgreSQL で機械学習する
http://pgsqldeepdive.blogspot.jp/2016/04/madlib-postgresql.html
■「svec」 とは何か
svec は、ゼロの多い sparse な配列を圧縮して保持するデータ型です。データ分析をしていると、頻繁に遭遇するデータの構造になります。
例えば、float8 の配列で以下のようにゼロが並ぶデータがあったとします。
'{0, 33,...40,000個のゼロ..., 12, 22 }'::float8[]すると、この配列は 320kB 以上のディスク容量またはメモリを消費することになります。ほとんど意味のないゼロを保持するだけのために、これだけのリソースを食ってしまいます。
svec は、この配列を以下のようにランレングス圧縮(RLE圧縮)することでデータサイズを縮小します。
'{1,1,40000,1,1}:{0,33,0,12,22}'::madlib.svecこのように圧縮することによって、5つの整数型と5つの浮動小数点型に集約され、データサイズが劇的に小さくなります。
このようなデータ型を用意することで、ディスク容量とメモリの消費を抑え、大量のデータの処理を可能にします。(もちろん、演算処理時には圧縮されたデータを展開しながら行いますので、そのCPUコストは発生します)
さらなる詳細はマニュアルを参照してください。
- MADlib: Sparse Vectors
https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html
■float8[] を svec に変換する
まず、TF-IDF 感動巨編3部作が完結した状態のテーブルから始めます。
- TF-IDFでデータベース内の類似テキストを検索する Part 3 (性能改善編)
http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-3.html
snaga=> \d pgsql_doc Table "public.pgsql_doc" Column | Type | Modifiers -----------+--------------------+----------------------------------------------------------- docid | integer | not null default nextval('pgsql_doc_docid_seq'::regclass) filename | text | not null html | text | not null plain | text | tf | jsonb | tfidf | jsonb | tfidf_vec | double precision[] | Indexes: "pgsql_doc_pkey" PRIMARY KEY, btree (docid) snaga=>この時、tfidf_vec のカラムは、以下のようにゼロの多い float8 の配列になっています。
snaga=> SELECT substring(tfidf_vec::text from 0 for 100) FROM pgsql_doc LIMIT 1; substring ----------------------------------------------------------------------------------------------------- {0,0,0,0,0,0,0,0.0328645679655572,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0 (1 row) snaga=>この float8[] を svec に変換するには、単に madlib.svec 型へキャストすれば完了です。
snaga=> SELECT substring(tfidf_vec::madlib.svec::text from 0 for 100) FROM pgsql_doc LIMIT 1; substring ----------------------------------------------------------------------------------------------------- {7,1,31,1,4,1,18,1,70,1,88,1,4,1,51,1,11,1,45,1,1,1,52,1,2,1,4,1,1,7,1,1,1,36,1,1,1,36,1,106,1,14,1 (1 row) snaga=>
■float8[] と svec のテーブルを用意する
まず、比較のために、docid, filename カラムと、float8[] の tfidf_vec カラム、もしくは svec の tfidf_svec カラムだけを保持するテーブル pgsql_doc_vec と pgsql_doc_svec を作成します。
tfidf_svec カラムは madlib.svec 型になっていることを確認します。
snaga=> CREATE TABLE pgsql_doc_vec AS SELECT docid,filename,tfidf_vec FROM pgsql_doc; SELECT 1304 snaga=> \d pgsql_doc_vec Table "public.pgsql_doc_vec" Column | Type | Modifiers -----------+--------------------+----------- docid | integer | filename | text | tfidf_vec | double precision[] | snaga=> CREATE TABLE pgsql_doc_svec AS SELECT docid,filename,tfidf_vec::madlib.svec as tfidf_svec FROM pgsql_doc; SELECT 1304 snaga=> \d pgsql_doc_svec Table "public.pgsql_doc_svec" Column | Type | Modifiers ------------+-------------+----------- docid | integer | filename | text | tfidf_svec | madlib.svec | snaga=>
■float8[] と svec のデータサイズを比較する
まず、2つのテーブルのデータサイズを比較してみます。
snaga=> \d+ List of relations Schema | Name | Type | Owner | Size | Description --------+---------------------+----------+-------+------------+------------- public | pgsql_doc | table | snaga | 407 MB | public | pgsql_doc_docid_seq | sequence | snaga | 8192 bytes | public | pgsql_doc_svec | table | snaga | 3872 kB | public | pgsql_doc_vec | table | snaga | 6752 kB | (4 rows) snaga=>上記を見て分かる通り、MADlib の svec 型を使ったテーブルの方が 3MB ほど小さくなっています。
このようにデータサイズを小さくすることによって、ディスクサイズの節約、I/O読み込みの抑制、バッファキャッシュの消費抑制などが実現され、ひいてはパフォーマンスの向上につながります。
■float8[] と svec のパフォーマンス比較
それでは、上記で作ったテーブルとカラムを使って、前回用いた「wal.html と類似のドキュメントを検索する」クエリでパフォーマンスを比較してみます。
float8[] 型を使う場合のクエリは以下の通りです。
EXPLAIN ANALYZE SELECT filename, euclidean_distance(tfidf_vec, (SELECT tfidf_vec FROM pgsql_doc_vec WHERE filename = 'wal.html') ) FROM pgsql_doc_vec ORDER BY 2;svec 型を使う場合のクエリは以下の通りです。
EXPLAIN ANALYZE SELECT filename, euclidean_distance(tfidf_svec::float8[], (SELECT tfidf_svec::float8[] FROM pgsql_doc_svec WHERE filename = 'wal.html') ) FROM pgsql_doc_svec ORDER BY 2;上記のクエリを5回ずつ実行した結果、
- float8[] 使用時:平均 4757.6 ミリ秒
- 4738.021, 4739.953, 4734.725, 4793.513, 4781.721
- svec 使用時:平均 4379.5 ミリ秒
- 4395.491, 4315.103, 4408.664, 4342.631, 4435.416
データが圧縮されたことによって、パフォーマンス改善が実現されているようです。
■まとめ
今回は、MADlib で提供されている svec 型を使ってみました。
今回のケースでは1割程度のパフォーマンス改善になりましたが、当然ながらデータ圧縮の程度によってパフォーマンス向上の度合いは変わってくるでしょうし、データ量によっても変わってくると思われます。
興味のある方は、ぜひトライしてみていただければと思います。
では、また。
0 件のコメント:
コメントを投稿