Skip to content

Commit f1363d8

Browse files
authored
Merge pull request #83 from lasp-lang/benchmark
Reduce compute time
2 parents d4c5da7 + 53953dc commit f1363d8

30 files changed

+615
-370
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ proper:
4343
lint:
4444
${REBAR} as lint lint
4545

46+
bench: compile
47+
bin/state_bench.sh
48+
4649
shell:
4750
${REBAR} shell --apps types
4851

bin/state_bench.sh

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env escript
2+
3+
%%! -pa _build/default/lib/types/ebin/
4+
5+
main(_) ->
6+
benchmark(state_gset),
7+
benchmark(state_awset),
8+
ok.
9+
10+
%% @private
11+
benchmark(Type) ->
12+
io:format("Running ~p~n", [Type]),
13+
R0 = create(Type),
14+
R1 = adds(Type, part(1), true, R0),
15+
R2 = adds(Type, part(2), false, R1),
16+
R3 = adds(Type, part(3), true, R2),
17+
analyze(R3).
18+
19+
%% @private
20+
analyze({_, Metrics}) ->
21+
lists:foreach(
22+
fun({MetricType, Values}) ->
23+
Min = lists:min(Values),
24+
Max = lists:max(Values),
25+
Average = lists:sum(Values) / length(Values),
26+
27+
io:format("- ~p:~n", [MetricType]),
28+
io:format(" > min: ~p~n", [Min]),
29+
io:format(" > max: ~p~n", [Max]),
30+
io:format(" > avg: ~p~n", [Average])
31+
end,
32+
Metrics
33+
).
34+
35+
%% @private
36+
create(Type) ->
37+
lists:foldl(
38+
fun(Replica, {StateIn, MetricsIn}) ->
39+
{Time, Result} = timer:tc(fun() -> Type:new() end),
40+
StateOut = orddict:store(Replica, Result, StateIn),
41+
MetricsOut = orddict:append(new,
42+
Time,
43+
MetricsIn),
44+
{StateOut, MetricsOut}
45+
end,
46+
{orddict:new(), orddict:new()},
47+
replicas()
48+
).
49+
50+
%% @private
51+
adds(Type, Seq, WithSync, R0) ->
52+
lists:foldl(
53+
fun(I, R1) ->
54+
%% each replica adds an element to the set
55+
R2 = lists:foldl(
56+
fun(Replica, {StateIn, MetricsIn}) ->
57+
Value = orddict:fetch(Replica, StateIn),
58+
Element = create_element(Replica, I),
59+
60+
{Time, Result} = timer:tc(
61+
fun() ->
62+
{ok, NewValue} = Type:mutate({add, Element}, Replica, Value),
63+
NewValue
64+
end
65+
),
66+
67+
StateOut = orddict:store(Replica, Result, StateIn),
68+
MetricsOut = orddict:append(mutate, Time, MetricsIn),
69+
{StateOut, MetricsOut}
70+
end,
71+
R1,
72+
replicas()
73+
),
74+
75+
{StateBeforeMerge, _} = R2,
76+
77+
R3 = case WithSync of
78+
true ->
79+
%% each replica synchronizes with its neighbors
80+
lists:foldl(
81+
fun(Replica, In) ->
82+
lists:foldl(
83+
fun(Neighbor, {StateIn, MetricsIn}) ->
84+
85+
%% get the state that would be received in a message
86+
RemoteState = orddict:fetch(Neighbor, StateBeforeMerge),
87+
%& get the current local state
88+
LocalState = orddict:fetch(Replica, StateIn),
89+
90+
{TimeDelta, Delta} = timer:tc(
91+
fun() ->
92+
%% which part of the remote state inflates the local state
93+
Type:delta(RemoteState, {state, LocalState})
94+
end
95+
),
96+
97+
{TimeBottom, false} = timer:tc(
98+
fun() ->
99+
%% check if the delta is bottom
100+
Type:is_bottom(Delta)
101+
end
102+
),
103+
104+
{TimeMerge, Merged} = timer:tc(
105+
fun() ->
106+
%% merge the received state
107+
Type:merge(LocalState, RemoteState)
108+
end
109+
),
110+
111+
{TimeInflation, true} = timer:tc(
112+
fun() ->
113+
%% check if there was a strict inflation
114+
Type:is_strict_inflation(LocalState, Merged)
115+
end
116+
),
117+
118+
StateOut = orddict:store(Replica, Merged, StateIn),
119+
MetricsOut0 = orddict:append(delta,
120+
TimeDelta,
121+
MetricsIn),
122+
MetricsOut1 = orddict:append(is_bottom,
123+
TimeBottom,
124+
MetricsOut0),
125+
MetricsOut2 = orddict:append(is_strict_inflation,
126+
TimeInflation,
127+
MetricsOut1),
128+
MetricsOut3 = orddict:append(merge,
129+
TimeMerge,
130+
MetricsOut2),
131+
{StateOut, MetricsOut3}
132+
end,
133+
In,
134+
neighbors(config(topology), Replica)
135+
)
136+
end,
137+
R2,
138+
replicas()
139+
);
140+
false ->
141+
R2
142+
end,
143+
144+
%% perform query and join_decompositions
145+
lists:foldl(
146+
fun(Replica, {StateIn, MetricsIn}) ->
147+
Value = orddict:fetch(Replica, StateIn),
148+
149+
{TimeQuery, _} = timer:tc(
150+
fun() ->
151+
Type:query(Value)
152+
end
153+
),
154+
155+
{TimeJD, _} = timer:tc(
156+
fun() ->
157+
Type:join_decomposition(Value)
158+
end
159+
),
160+
161+
MetricsOut0 = orddict:append(query,
162+
TimeQuery,
163+
MetricsIn),
164+
MetricsOut1 = orddict:append(join_decompositions,
165+
TimeJD,
166+
MetricsOut0),
167+
{StateIn, MetricsOut1}
168+
end,
169+
R3,
170+
replicas()
171+
)
172+
end,
173+
R0,
174+
Seq
175+
).
176+
177+
%% @private
178+
replicas() ->
179+
lists:seq(1, config(replica_number)).
180+
181+
%% @private Get the neighbors of a replica in a ring topology.
182+
neighbors(ring, Replica) ->
183+
ReplicaNumber = config(replica_number),
184+
185+
Left = case Replica of
186+
1 ->
187+
ReplicaNumber;
188+
_ ->
189+
Replica - 1
190+
end,
191+
Right = case Replica of
192+
ReplicaNumber ->
193+
1;
194+
_ ->
195+
Replica + 1
196+
end,
197+
[Left, Right].
198+
199+
%% @private
200+
part(Number) ->
201+
AddNumber = config(add_number),
202+
D2 = AddNumber div 2,
203+
D4 = AddNumber div 4,
204+
case Number of
205+
1 ->
206+
lists:seq(1, D4); %% from 0% to 25%
207+
2 ->
208+
lists:seq(D4 + 1, D2 + D4); %% from 25% to 75%
209+
3 ->
210+
lists:seq(D2 + D4 + 1, AddNumber) %% from 75% to 100%
211+
end.
212+
213+
%% @private
214+
create_element(Replica, I) ->
215+
integer_to_list(Replica) ++ "#####" ++ integer_to_list(I).
216+
217+
218+
%% @private config
219+
config(topology) -> ring;
220+
config(replica_number) -> 6;
221+
config(add_number) -> 100.

src/causal_context.erl

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
next_dot/2,
3535
is_empty/1,
3636
is_element/2,
37-
union/2
37+
union/2,
38+
to_dot/1
3839
]).
3940

4041
-export_type([causal_context/0]).
@@ -52,12 +53,12 @@ new() ->
5253
%% @doc Create a CausalContext from a DotSet.
5354
-spec from_dot_set(dot_set()) -> causal_context().
5455
from_dot_set(DotSet) ->
55-
lists:foldl(
56+
dot_set:fold(
5657
fun(Dot, CausalContext) ->
5758
causal_context:add_dot(Dot, CausalContext)
5859
end,
5960
causal_context:new(),
60-
dot_set:to_list(DotSet)
61+
DotSet
6162
).
6263

6364
%% @doc Return a list of dots from a CausalContext.
@@ -84,22 +85,21 @@ dots({Compressed, DotSet}) ->
8485
add_dot({Actor, Sequence}=Dot, {Compressed0, DotSet0}=CC) ->
8586
Current = orddict_ext:fetch(Actor, Compressed0, 0),
8687

87-
case is_element(Dot, CC) of
88+
case Sequence == Current + 1 of
8889
true ->
89-
%% if dot already in the cc, ignore
90-
CC;
90+
%% update the compressed component
91+
Compressed1 = orddict:store(Actor, Sequence, Compressed0),
92+
{Compressed1, DotSet0};
9193
false ->
92-
case Sequence == Current + 1 of
94+
case Sequence > Current + 1 of
9395
true ->
94-
%% if previous dot already in the compressed component
95-
%% add it there
96-
Compressed1 = orddict:store(Actor, Sequence, Compressed0),
97-
compress({Compressed1, DotSet0});
98-
false ->
99-
%% otherwise store in the DotSet
96+
%% store in the DotSet if in the future
10097
DotSet1 = dot_set:add_dot(Dot, DotSet0),
101-
{Compressed0, DotSet1}
102-
end
98+
{Compressed0, DotSet1};
99+
false ->
100+
%% dot already in the CausalContext.
101+
CC
102+
end
103103
end.
104104

105105
%% @doc Get `dot_actor()''s next dot
@@ -118,9 +118,7 @@ is_empty({Compressed, DotSet}) ->
118118
-spec is_element(dot_store:dot(), causal_context()) -> boolean().
119119
is_element({Actor, Sequence}=Dot, {Compressed, DotSet}) ->
120120
Current = orddict_ext:fetch(Actor, Compressed, 0),
121-
BelongsToCompressed = Sequence =< Current,
122-
BelongsToDotSet = dot_set:is_element(Dot, DotSet),
123-
BelongsToCompressed orelse BelongsToDotSet.
121+
Sequence =< Current orelse dot_set:is_element(Dot, DotSet).
124122

125123
%% @doc Merge two Causal Contexts.
126124
-spec union(causal_context(), causal_context()) -> causal_context().
@@ -139,10 +137,15 @@ union({CompressedA, DotSetA}, {CompressedB, DotSetB}) ->
139137
%% component.
140138
-spec compress(causal_context()) -> causal_context().
141139
compress({Compressed, DotSet}) ->
142-
lists:foldl(
140+
dot_set:fold(
143141
fun(Dot, CausalContext) ->
144142
add_dot(Dot, CausalContext)
145143
end,
146144
{Compressed, dot_set:new()},
147-
dot_set:to_list(DotSet)
145+
DotSet
148146
).
147+
148+
%% @private Convert a CausalContext with a single dot, to that dot.
149+
-spec to_dot(causal_context()) -> dot_store:dot().
150+
to_dot({[Dot], []}) -> Dot;
151+
to_dot({[], [Dot]}) -> Dot.

src/dot_map.erl

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
-export([
3232
new/0,
3333
is_empty/1,
34-
is_element/3,
3534
fetch_keys/1,
3635
fetch/3,
37-
store/3
36+
store/3,
37+
merge/4,
38+
any/2,
39+
fold/3
3840
]).
3941

4042
-type dot_map() :: dot_store:dot_map().
@@ -49,13 +51,10 @@ new() ->
4951
is_empty(DotMap) ->
5052
orddict:is_empty(DotMap).
5153

52-
%% @doc Check if a dot belongs to the DotMap.
53-
-spec is_element(dot_store:type(), dot_store:dot(), dot_map()) ->
54-
boolean().
55-
is_element(DotStoreType, Dot, DotMap) ->
56-
DotSet = state_causal_type:dots({dot_map, DotStoreType}, DotMap),
57-
dot_set:is_element(Dot, DotSet).
58-
54+
%% @doc Given a key, a DotMap and a default,
55+
%% return:
56+
%% - the correspondent value, if key present in the DotMap
57+
%% - default, otherwise
5958
-spec fetch(term(), dot_map(), dot_store:dot_store() | undefined) ->
6059
dot_store:dot_store().
6160
fetch(Key, DotMap, Default) ->
@@ -71,3 +70,40 @@ fetch_keys(DotMap) ->
7170
-spec store(term(), dot_store:dot_store(), dot_map()) -> dot_map().
7271
store(Key, DotStore, DotMap) ->
7372
orddict:store(Key, DotStore, DotMap).
73+
74+
%% @doc Merge two DotMap.
75+
-spec merge(function(), dot_store:dot_store(),
76+
dot_map(), dot_map()) -> dot_map().
77+
merge(_Fun, _Default, [], []) ->
78+
[];
79+
merge(Fun, Default, [{Key, ValueA} | RestA], []) ->
80+
do_merge(Fun, Default, Key, ValueA, Default, RestA, []);
81+
merge(Fun, Default, [], [{Key, ValueB} | RestB]) ->
82+
do_merge(Fun, Default, Key, Default, ValueB, [], RestB);
83+
merge(Fun, Default, [{Key, ValueA} | RestA],
84+
[{Key, ValueB} | RestB]) ->
85+
do_merge(Fun, Default, Key, ValueA, ValueB, RestA, RestB);
86+
merge(Fun, Default, [{KeyA, ValueA} | RestA],
87+
[{KeyB, _} | _]=RestB) when KeyA < KeyB ->
88+
do_merge(Fun, Default, KeyA, ValueA, Default, RestA, RestB);
89+
merge(Fun, Default, [{KeyA, _} | _]=RestA,
90+
[{KeyB, ValueB} | RestB]) when KeyA > KeyB ->
91+
do_merge(Fun, Default, KeyB, Default, ValueB, RestA, RestB).
92+
93+
do_merge(Fun, Default, Key, ValueA, ValueB, RestA, RestB) ->
94+
case Fun(ValueA, ValueB) of
95+
Default ->
96+
merge(Fun, Default, RestA, RestB);
97+
Value ->
98+
[{Key, Value} | merge(Fun, Default, RestA, RestB)]
99+
end.
100+
101+
%% @doc True if Pred is true for at least one entry in the DotMap.
102+
-spec any(function(), dot_map()) -> boolean().
103+
any(Pred, DotMap) ->
104+
lists:any(Pred, DotMap).
105+
106+
%% @doc Fold a DotMap.
107+
-spec fold(function(), term(), dot_map()) -> term().
108+
fold(Fun, AccIn, DotMap) ->
109+
orddict:fold(Fun, AccIn, DotMap).

0 commit comments

Comments
 (0)