バイナリ形式にするだけでメモリ節約プログラミング
比較条件に関係しない値なら、バイナリにする価値あり
小さい数値を多く扱う場合は、バイナリ(可変長表現)にすると結構小さくできます。
データ量が小さくなれば、DBからの取得速度が向上し、アプリのメモリ上にもより多くのデータを保持できるようになります。
ただ、バイナリ形式のままだと大小比較などの計算ができないので、必要に応じて数値型に戻してあげる必要があります。
コード
今回比較に利用したコードは、githubに上げてあります。
https://github.com/TakiTake/seiseki
数値とバイナリの速度比較
学校の生徒100人が毎日テストを5科目受けたときの成績管理用DBを想定
バイナリ形式のDBは、科目カラムに5科目分のデータを保持させます。
テストデータなんで、土日祝日お構いなしにテスト受けてますw
ノーマルなDB
カラム名 | 型 |
日付 | text |
学籍番号 | text |
科目1 | integer |
科目2 | integer |
科目3 | integer |
科目4 | integer |
科目5 | integer |
バイナリ形式のDB
カラム名 | 型 |
日付 | text |
学籍番号 | text |
科目 | blob |
結果
1年分のデータ
size
normal 1.9MB binary 1.7MB
insert
user system total real normal 4.740000 11.920000 16.660000 ( 94.378284) binary 4.660000 11.920000 16.580000 ( 93.093927)
select
user system total real normal 0.210000 0.010000 0.220000 ( 0.221661) binary 0.190000 0.010000 0.200000 ( 0.201906)
5年分のデータ
size
normal 9.6MB binary 8.8MB
insert
user system total real normal 23.860000 58.860000 82.720000 (476.608330) binary 23.530000 58.400000 81.930000 (507.330652)
select
user system total real normal 1.190000 0.070000 1.260000 ( 1.300539) binary 0.980000 0.070000 1.050000 ( 1.078891)
10年分のデータ
size
normal 19MB binary 18MB
insert
user system total real normal 47.350000 118.030000 165.380000 (920.600014) binary 46.610000 118.380000 164.990000 (935.340207)
select
user system total real normal 2.140000 0.100000 2.240000 ( 2.236974) binary 1.790000 0.100000 1.890000 ( 1.891564)
なぜ、サイズが小さくなるのか?
小さい数値を表すのに、8 byte もいらないよね
環境差分はありますが、自分の環境ではRubyのFixnumは、8 byte でした。
0~255までの数値なら 1byte で表すことができるので、固定長ではなく可変長で数値を扱えば、 7byte 削減できというわけです。
どうやって変換すればいいの?
数列 -> バイナリ文字列
Array#pack のテンプレートに w(BER-compressed integer) を指定するだけです。
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-pack
バイナリ文字列 -> 数列
String#unpack テンプレートに w(BER-compressed integer) を指定するだけです。
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-unpack
応用編
ただの文字列なので、文字列結合もできます
[33, 49, 300, 99, 1000].pack('w5') // "!1\x82,c\x87h"
'abc' + [33, 49, 300, 99, 1000].pack('w5') // "abc!1\x82,c\x87h"
また、こういった文字列処理には、StringScannerが便利です。
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/strscan/rdoc/StringScanner.html
先頭から、3 byte 取得
str = 'abc' + [33, 49, 300, 99, 1000].pack('w5') ss = StringScanner.new(str) ss.peek(3) // "abc"
3 byte 分ポインタを進める
ss.pos += 3
ここから後ろは、可変長なので何byte分取得すればいいかわかりません。
そんなときは、文字列中にbyte数を持たせてあげれば、そこから判断できます。
サイズを含む文字列
packed = [33, 49, 300, 99, 1000].pack('w5') # packした文字列のサイズをさらに、packする # C(8-bit unsigned)にすることで、この1byteを読めば続く文字列のサイズが分かる # 文字列のサイズが、8bitで表しきれない場合は、テンプレートにS, L, Qを適宜使う packed_size = [packed.size].pack('C') str = 'abc' + packed_size + packed ss = StringScanner.new(str) # "abc"の3byte分進める ss.pos += 3 # 1byte取得して、unpackすることで保存しておいたサイズを取得 # unpackは、配列で返ってくるので、最初の要素を取得している ss.peek(1).unpack('C').first // 7 # サイズを格納している、1byte分進める ss.pos += 1 # 最後に、先ほど判明した7byte分取得して、unpackすると # めでたく最初の数列が取得できる ss.peek(7).unpack('w5') // [33, 49, 300, 99, 1000]