Posted at

ETSのテーブルタイプ毎の書き込み時の違い

More than 3 years have passed since last update.

「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を使ったときの挙動に違いがある模様。結構深そう。