K.Sasada's Home Page

YAML を Ruby で使う


YAML とは

YAML Ain't Markup Language だそうです。ain't ってなんのことか知らなかったんですが、 am not とか isn't とか hasn't とか haven't とかのことらしい。

書式とか

一般的な型について、どのように記述するか、というのが決まっている。Yaml Cookbook に詳しい。英語なんて読まなくても、ソースを見ればわかる。

基本的に接頭語とインデントで表現。

YAML を Ruby で使う

Ruby 1.8 から YAML を利用するためのライブラリが標準添付なので、何も考えず、 require 'yaml' か、 require 'yaml/store' をするだけです。

YAML を Ruby で美味しく使うためのリファレンスは参考文献のyaml4r のページでダウンロードできます。chm とかもあります。

YAML にする

Ruby の(ほとんどの)オブジェクトは、YAML文字列 へ簡単に変換することができます。

require 'yaml' # 必要
obj.to_yaml    # YAML へ変換

例としてはこんな感じ。

require 'yaml'

ary = [1,[2,[3]],4]
map = {'a' => 'AAA', 'b' => 'BBB', 'c' => 'CCC',}
class Video
  attr_accessor :title, :year, :rating
  def initialize( t, y, r )
    @title = t
    @year = y
    @rating = r
  end
end


puts ary.to_yaml
puts map.to_yaml
puts 'string desu-'.to_yaml
puts true.to_yaml
puts(/abc/.to_yaml)
puts Object.new.to_yaml
puts 12345678901234567890.to_yaml
puts 3.14159.to_yaml
puts Time.now.to_yaml
puts "abc\ndef\nghi".to_yaml
puts :sym.to_yaml
puts Video.new('a','b','c').to_yaml

#=>
--- 
- 1
- 
  - 2
  - 
    - 3
- 4
--- 
a: AAA
b: BBB
c: CCC
--- "string desu-"
--- true
--- !ruby/regexp "/abc/"
--- !ruby/object:Object {}
--- 12345678901234567890
--- 3.14159
--- 2003-08-12 01:12:48.875000 +09:00
--- >-
abc

def

ghi
--- !ruby/sym sym
--- !ruby/object:Video 
rating: c
title: a
year: b

また、YAML::load によって、to_yaml した結果の文字列を Ruby オブジェクトに復元することができます。

obj = ...
decoded_obj = YAML::load(obj.to_yaml)

Marshal の代わりに使う

Marshal は Ruby のオブジェクトをある一定の方式でバイナリへ変換したり、それを復元したりするためのものです。YAML は ruby のオブジェクトを自由に表現できるらしいので、Marshal でやっていたことを YAML でできる、ということです。

次のような対応関係があります。

obj = ... # 略

bin = Marshal.dump(obj)        # オブジェクトからバイナリへ
loaded_obj = Marshal.load(bin) # バイナリからオブジェクトへデコード

require 'yaml'
yml = obj.to_yaml              # オブジェクトからYAML へ
loaded_obj = YAML::load(yml)   # YAML からオブジェクトへデコード

もちろん、Marshal では表現できない入出力オブジェクトや Proc オブジェクトなどは YAML でも表現できません。

PStore の代わりに使う

PStore とは、Marshal を用いたオブジェクトデータベースだそうです。

YAML は Marshal の機能を置き換えることができるため、PStore も YAML で実現できるはず、ということで実装したのが YAML::Store だそうです。

既にある PStore を利用したプログラムに、require 'yaml/store' を加え、PStore という文字列を YAML::Store という文字列に置換すれば大丈夫です。

require 'yaml/store'
db = YAML::Store.new('yaml_db')
db.transaction{
  db['data'] = obj
  val1 = db['val1']
}

(PStore を知っていれば)何も考えることはありませんね。

YAML::parse

ごめん、よく読んでない。けど、DOM で XPath みたいなのを狙ってるらしい。

性能

気になる(俺だけかもしれない)性能についてみてみました。

サイズ

某投票システムでは、次のようになりました。

# Marshaled 
# binary 104 バイト

# YAML テキストファイル 156 バイト
--- 
data: !ruby/object:Vote::VoteItem 
  cnt: 
    - 3
    - 0
    - 4
  sel: 
    - "です。"
    - "ノー。"
    - "どちらとも言えない。"
  title: "幸せ?"

まぁ、やっぱりちょっと大きいです。可読性のために冗長性を取る、最近の流行ですね。

処理速度

Marshal で dump するのと、to_yaml するのの性能比を測ってみます。

## to_yaml vs marshal about
## [1,2,3,{:a => "A", :b => "B", :c => "C"},"string",3.14]
              user     system      total        real
to_yaml   4.219000   0.078000   4.297000 (  4.453000)
marshal   0.032000   0.000000   0.032000 (  0.031000)

2桁くらい違います。

では、PStore と YAML::Store の速度性能差を出して見ます。

読み込み。

## read
                  user     system      total        real
yaml::store   8.078000   1.812000   9.890000 ( 10.375000)
PStore        0.750000   0.516000   1.266000 (  1.375000)

書き込み。

=========================================================
## write
                  user     system      total        real
yaml::store   7.890000   2.000000   9.890000 ( 10.141000)
PStore        0.703000   0.531000   1.234000 (  1.375000)

一桁ほどの性能差です。ファイルシステムへの読み書きのオーバーヘッドで、to_yaml の速度差が緩和されたんですかね。

まぁ、この結果から、大規模でクリティカルな用途では、あんまり利用したくないことがわかります(しねーよ)。

まぁ、ちょこっと使う分には、あとで手を入れやすいしいいんじゃないですかね。

参考文献

Sasada Koichi / sasada@namikilab.tuat.ac.jp
$Date: 2003/02/26 13:07:29 $