Skip to content

Instantly share code, notes, and snippets.

@sile
Last active August 29, 2015 14:08
Show Gist options
  • Save sile/85232feebbac43e79955 to your computer and use it in GitHub Desktop.
Save sile/85232feebbac43e79955 to your computer and use it in GitHub Desktop.
ErlangでKVS的なプロセスのRead性能をコア数に対してスケールさせる方法 ref: http://qiita.com/sile/items/f3a2d2bea0135847523d
-module(bench_parallel).
-export([bench/4]).
%% @doc ベンチマーク関数
%%
%% 一秒間に何回読み込み(検索)処理を行えるかを返す
-spec bench(module(), non_neg_integer(), non_neg_integer(), pos_integer()) -> ReadsPerSecond::non_neg_integer().
bench(Module, EntryCount, ReadCount, ClientCount) ->
%% KVSプロセスの起動
{ok, Pid} = Module:start_link(),
%% 最初に要素を登録しておく
ok = lists:foreach(
fun (I) -> Module:store(I, I, Pid) end,
lists:seq(0, EntryCount - 1)),
_ = timer:sleep(10),
%% 各クライアントプロセスが担当する読み込み処理の個数を計算する(端数の処理はいい加減)
PerProcessReadCount = ReadCount div ClientCount,
%% 読み込み性能を測定する
{Elapsed, _} =
timer:tc(
fun () ->
%% クライアントプロセスを起動
ok = lists:foreach(
fun (_) ->
spawn_monitor(bench_serial, find_loop,
[PerProcessReadCount, Module, Module, EntryCount])
end,
lists:seq(1, ClientCount)),
%% 全てのクライアントの処理が終わるまで待機
wait_loop(ClientCount)
end),
%% KVSプロセスの停止
_ = unlink(Pid),
_ = exit(Pid, kill),
(ReadCount * 1000 * 1000) div Elapsed.
-spec wait_loop(non_neg_integer()) -> ok.
wait_loop(0) -> ok;
wait_loop(N) ->
receive
{'DOWN', _, _, _, normal} -> wait_loop(N - 1)
end.
-module(bench_serial).
-export([bench/3]).
-export([find_loop/4]). % 後で参照したいので公開関数にしておく
%% @doc ベンチマーク関数
%%
%% 一秒間に何回読み込み(検索)処理を行えるかを返す
-spec bench(module(), non_neg_integer(), non_neg_integer()) -> ReadsPerSecond::non_neg_integer().
bench(Module, EntryCount, ReadCount) ->
%% 最初に要素を登録しておく
Map =
lists:foldl(
fun (I, Acc) -> Module:store(I, I, Acc) end,
Module:new(),
lists:seq(0, EntryCount - 1)),
%% 読み込み性能を測定する
{Elapsed, _} =
timer:tc(
fun () ->
find_loop(ReadCount, Module, Map, EntryCount)
end),
(ReadCount * 1000 * 1000) div Elapsed.
%% @doc 要素の検索処理を指定回数分だけ行う
-spec find_loop(non_neg_integer(), module(), term(), non_neg_integer()) -> ok.
find_loop(0, _, _, _) ->
ok;
find_loop(Rest, Module, Map, Limit) ->
I = Rest rem Limit,
{ok, I} = Module:find(I, Map),
find_loop(Rest - 1, Module, Map, Limit).
%% dictを使ったKVS的サーバプロセス (必要最低限の実装)
-module(dict_kvs).
-export([start_link/0, store/3, find/2]).
-record(state, {map :: dict:dict()}).
%%% external functions
-spec start_link() -> {ok, pid()}.
start_link() ->
Pid = spawn_link(fun() -> loop(#state{map = dict:new()}) end),
true = register(?MODULE, Pid), % モジュール名 == プロセス名
{ok, Pid}.
-spec store(term(), term(), pid()|atom()) -> ok.
store(Key, Value, Server) ->
_ = Server ! {store, Key, Value},
ok.
-spec find(term(), pid()|atom()) -> {ok, Value::term()} | error.
find(Key, Server) ->
% メッセージパッシングを使って、サーバに値を問い合わせる
Ref = make_ref(),
_ = Server ! {find, self(), Ref, Key},
receive
{Ref, Result} -> Result
end.
%%% internal functions
-spec loop(#state{}) -> no_return().
loop(State) ->
receive
{store, Key, Value} ->
Map = dict:store(Key, Value, State#state.map),
loop(State#state{map = Map});
{find, From, Ref, Key} ->
_ = From ! {Ref, dict:find(Key, State#state.map)},
loop(State)
end.
%% 名前付きETSを使ったKVS的サーバプロセス (必要最低限の実装)
-module(ets_kvs).
-export([start_link/0, store/3, find/2]).
-record(state, {map :: ets:tid()}).
%%% external functions
-spec start_link() -> {ok, pid()}.
start_link() ->
Pid = spawn_link(fun() -> loop(#state{map = ets_map:new(?MODULE)}) end),
true = register(?MODULE, Pid), % モジュール名 == プロセス名
{ok, Pid}.
-spec store(term(), term(), pid()|atom()) -> ok.
store(Key, Value, Server) ->
_ = Server ! {store, Key, Value},
ok.
-spec find(term(), pid()|atom()) -> {ok, Value::term()} | error.
find(Key, _Server) ->
%% 検索時にはサーバプロセスに問い合わせることなく、直接ETSテーブルにアクセスする
ets_map:find(Key, ?MODULE).
%%% internal functions
-spec loop(#state{}) -> no_return().
loop(State) ->
receive
{store, Key, Value} ->
Map = ets_map:store(Key, Value, State#state.map),
loop(State#state{map = Map}) % dict_kvsに合わせてmapフィールドの更新を行っている(実質的には意味はない)
end.
-module(ets_map).
-export([new/0, store/3, find/2]). % dictに合わせた公開関数
-export([new/1]). % 名前付きテーブル生成用関数
%% @doc 名前なしテーブルを生成する (単体性能測定用)
-spec new() -> ets:tid().
new() ->
ets:new(?MODULE, [set, protected, {read_concurrency, true}]).
%% @doc 名前付きターブルを生成する (サーバ性能測定用)
-spec new(Name::atom()) -> ets:tid().
new(Name) ->
ets:new(Name, [named_table, set, protected, {read_concurrency, true}]).
-spec store(Key::term(), Value::term(), ets:tid()) -> ets:tid().
store(Key, Value, Map) ->
true = ets:insert(Map, {Key, Value}),
Map.
-spec find(Key::term(), ets:tid()) -> {ok, Value::term()} | error.
find(Key, Map) ->
case ets:lookup(Map, Key) of
[{_, Value}] -> {ok, Value};
[] -> error
end.
%% ets_map
> bench_serial:bench(ets_map, 1000, 10000000). % NOTE: 実行が終わっても生成されたETSテーブルは自動では回収されない
3377147 % 337万/sec
%% dict
> bench_serial:bench(dict, 1000, 10000000).
2326308 % 232万/sec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment