Current section

Files

Jump to
nitrogen_core src lib wf_fastmap.erl
Raw

src/lib/wf_fastmap.erl

%% vim: ft=erlang ts=4 sw=4 sts=4 et
-module(wf_fastmap).
-export([
build/1,
get/0,
get/1,
set/2,
set/1,
init/0,
bench/0
]).
-compile({no_auto_import, [get/1, get/0]}).
-include("wf.hrl").
-include_lib("syntax_tools/include/merl.hrl").
%% This works like mochiglobal and persistent_term. For the use-case this is
%% optimized for (very small terms for keys and values), it's between a 2x and
%% 10x speedup over persistent_term.
%%
%% It works like mochiglobal in that it compiles a lookup module (called
%% wf_fastmap_worker).
%%
%% This is not recommended to be used with large values (like 1MB+ binaries).
%% In that situation, use persistent_term, which will be *much* faster.
%%
%% Also, like mochiglobal, this is *very* slow at writes. This is designed for
%% something that might be written to when the Erlang VM starts, and then very
%% rarely after that (like during a release upgrade, or if a config needs to
%% change).
%
%% The produced module looks something like this:
%%
%% -module(wf_fastmap_worker).
%% -compile(export_all).
%%
%% get(some_key) -> Val;
%% get(some_other_key) -> Val;
%% ...
%% get(_) -> throw().
%% ...
-define(PTERM_FASTMAP_CONFIG, wf_fastmap_config).
store_config(Map) when is_map(Map) ->
wf_utils:pterm(?PTERM_FASTMAP_CONFIG, Map).
get_config() ->
wf_utils:pterm(?PTERM_FASTMAP_CONFIG).
init() ->
Config = case get_config() of
undefined ->
default_config();
Cfg ->
Cfg
end,
build(Config).
default_config() ->
%% Currently, the only defaults are the grid_system defaults, however,
%% if new defaults are defined, the Configs can be combined (this is why
%% we're not just calling wf_grid_system:init/0). Doing it the way we're
%% currently doing it will only require then setting the value once.
Config = wf_grid_system:initial_config(),
Config.
set(KV) when is_list(KV) ->
NewMap = maps:from_list(KV),
set(NewMap);
set(NewMap) when is_map(NewMap) ->
CurrentMap = get(),
FinalMap = maps:merge(CurrentMap, NewMap),
build(FinalMap).
get() ->
wf_fastmap_worker:get().
get(Key) ->
wf_fastmap_worker:get(Key).
set(Key, Val) ->
wf_fastmap_worker:set(Key, Val).
build(Map) when is_map(Map) ->
store_config(Map),
BuildMsg = "Building wf_fastmap_worker with this config:",
TooMuch = "(If you see this more than a few times per day,\n you're probably updating this too much)",
msg(wf:f("~s~n~p~n~n~s", [BuildMsg,Map, TooMuch])),
build_inner(maps:to_list(Map)).
build_inner(PL) ->
Modtext = build_module(PL),
Forms = try merl:quote(Modtext)
catch E:T:S ->
msg(wf:f("Failed to parse wf_fastmap_worker module with the text: ~n~ts",[Modtext])),
throw({E, T, S})
end,
Res = merl:compile_and_load(Forms),
%logger:info("Building Module:~n~ts~n",[Modtext]),
case Res of
{ok, _} -> ok;
error ->
msg(wf:f("Unable to build wf_fastmap_worker module with the text:~n~ts",[Modtext])),
error
end.
build_module(PL) ->
lists:flatten([
build_header(),
build_exports(PL)
]).
build_header() ->
"-module(wf_fastmap_worker).\n"
"-compile({no_auto_import, [get/1, get/0]}).\n"
"-export([get/1, get/0, set/2]).\n\n".
build_exports(PL) ->
[
build_keys_0(PL),
build_get_1(PL),
build_get_0(),
build_set_2()
].
build_get_1(PL) ->
[
[build_clause_get_1(KV) || KV <- PL],
build_clause_get_1_catchall()
].
build_clause_get_1({Key, Value}) ->
wf:f("get(~p) -> ~p;~n", [Key, Value]).
build_clause_get_1_catchall() ->
"get(X) -> throw({wf_fastmap_key_not_found, X}).\n\n".
build_keys_0(PL) ->
Keys = proplists:get_keys(PL),
wf:f("keys() -> ~p.~n~n",[Keys]).
build_get_0() ->
"get() -> maps:from_list([{K, get(K)} || K <- keys()]).\n\n".
build_set_2() ->
"set(Key, Val) ->
Map = get(),
Map2 = maps:put(Key, Val, Map),
wf_fastmap:build(Map2).\n\n".
msg(Msg) ->
logger:notice("Nitrogen Global FastMap: ~ts~n",[Msg]).
-define(TESTKEY(K), {nitrogen, K}).
bench() ->
QuickMap = #{a=>b, b=>d, c=>f,
d=>crypto:strong_rand_bytes(100)},
{BTime1, _} = timer:tc(fun() ->
maps:map(fun(K, V) ->
persistent_term:put(?TESTKEY(K), V)
end, QuickMap)
end),
{BTime2, _} = timer:tc(fun() ->
build(QuickMap)
end),
JustKeys = maps:keys(QuickMap),
Keys = lists:map(fun(X) ->
lists:nth(X rem 3 + 1, JustKeys)
end, lists:seq(1, 1000000)),
{Time1, _Res1} = timer:tc(fun() ->
[persistent_term:get(?TESTKEY(K)) || K <- Keys]
end),
{Time2, _Res2} = timer:tc(fun() ->
[wf_fastmap:get(K) || K <- Keys]
end),
{results, #{
pterm_time=>Time1,
fastmap_time=>Time2,
pterm_store=>BTime1,
fastmap_store=>BTime2}
}.