「Elixir in Action」でETSについて読んだので、各テーブルタイプ(set, ordered_set, bag, duplicate_bag)毎の挙動の違いを調べた。言語はErlang/Elixirのどちらでもよいが、今回はElixirで書く。環境は以下。
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.2.3) - press Ctrl+C to exit (type h() ENTER for help)
setとbagの違い
書き込み時に、同じキーの行がすでに存在するときの挙動が異なる。setは上書きを行い、bagは両方保持する。
set_table = :ets.new(:set_table, [:set])
:ets.insert(set_table, {:foo, 1})
:ets.insert(set_table, {:foo, 2})
:ets.lookup(set_table, :foo) #=> [foo: 2]
bag_table = :ets.new(:bag_table, [:bag])
:ets.insert(bag_table, {:foo, 1})
:ets.insert(bag_table, {:foo, 2})
:ets.lookup(bag_table, :foo) #=> [foo:1, foo: 2]
setとordered_setの違い
:ets.first/1
, :ets.next/2
, :ets.prev/2
などで返ってくるキーの順番が異なる。setは内部で保持している順に返ってくるが、ordered_setはキーでソートしてある。
set_table = :ets.new(:set_table, [:set])
:ets.insert(set_table, {:foo, 1})
:ets.insert(set_table, {:bar, 2})
:ets.insert(set_table, {:baz, 3})
:ets.insert(set_table, {:hoge, 4})
:ets.insert(set_table, {:fuga, 5})
k = :ets.first(set_table) #=> :bar
k = :ets.next(set_table, k) #=> :baz
k = :ets.next(set_table, k) #=> :fuga
k = :ets.next(set_table, k) #=> :hoge
k = :ets.next(set_table, k) #=> :foo
k = :ets.next(set_table, k) #=> :"$end_of_table"
ordered_set_table = :ets.new(:ordered_set_table, [:ordered_set])
:ets.insert(ordered_set_table, {:foo, 1})
:ets.insert(ordered_set_table, {:bar, 2})
:ets.insert(ordered_set_table, {:baz, 3})
:ets.insert(ordered_set_table, {:hoge, 4})
:ets.insert(ordered_set_table, {:fuga, 5})
k = :ets.first(ordered_set_table) #=> :bar
k = :ets.next(ordered_set_table, k) #=> :baz
k = :ets.next(ordered_set_table, k) #=> :foo
k = :ets.next(ordered_set_table, k) #=> :fuga
k = :ets.next(ordered_set_table, k) #=> :hoge
k = :ets.next(ordered_set_table, k) #=> :"$end_of_table"
bagとduplicate_bagの違い
全く同じタプルを挿入したときの挙動が異なる。bagでは同じものとして扱われ1つしか残らないが、duplicate_bagでは別物として保持される。
bag_table = :ets.new(:bag_table, [:bag])
:ets.insert(bag_table, {:foo, 1})
:ets.insert(bag_table, {:foo, 1})
:ets.insert(bag_table, {:foo, 2})
:ets.lookup(bag_table, :foo) #=> [foo: 1, foo: 2]
duplicate_bag_table = :ets.new(:duplicate_bag_table, [:duplicate_bag])
:ets.insert(duplicate_bag_table, {:foo, 1})
:ets.insert(duplicate_bag_table, {:foo, 1})
:ets.insert(duplicate_bag_table, {:foo, 2})
:ets.lookup(duplicate_bag_table, :foo) #=> [foo: 1, foo: 1, foo: 2]
他にも読み出し(lookup
, select
, match
)の速度や、:ets.safe_fixtable/2
を使ったときの挙動に違いがある模様。結構深そう。