File 2722-Use-maps-from_list-1-on-maps-intersect_with-3.patch of Package erlang
From b06c691cf1ed4c4af81780551c9fabe2100ee1eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 1 Dec 2020 13:40:23 +0100
Subject: [PATCH] Use maps:from_list/1 on maps:intersect_with/3
The current implementation is not efficient when
the maps being compared are mostly distinct.
It happens that collecting all keys and building
the map once at the end is the most efficient
way of implementing intersection.
Benchmarks show this approch is often 4x faster in
the mostly distinct cases, otherwise it behaves
within 5-10% of the current implementation. The only
case it is considerably slower is when working with
small sets of integers (~10 elements) but those
are done in nanoseconds anyway.
Benchmaking code and results:
https://gist.github.com/josevalim/759f3a9c2613119d3e38cd4224840d32
---
lib/stdlib/src/maps.erl | 45 ++++++++++++++++++++++-------------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl
index 1f2b774eb9..7045108dab 100644
--- a/lib/stdlib/src/maps.erl
+++ b/lib/stdlib/src/maps.erl
@@ -73,13 +73,18 @@ from_list(_) -> erlang:nif_error(undef).
Map3 :: #{Key => Value2}.
intersect(Map1, Map2) when is_map(Map1), is_map(Map2) ->
- intersect_with(fun intersect_combiner/3, Map1,Map2);
+ case map_size(Map1) =< map_size(Map2) of
+ true ->
+ intersect_with_small_map_first(fun intersect_combiner_v2/3, Map1, Map2);
+ false ->
+ intersect_with_small_map_first(fun intersect_combiner_v1/3, Map2, Map1)
+ end;
intersect(Map1, Map2) ->
erlang:error(error_type_two_maps(Map1, Map2),
[Map1,Map2]).
-intersect_combiner(_K, _V1, V2) ->
- V2.
+intersect_combiner_v1(_K, V1, _V2) -> V1.
+intersect_combiner_v2(_K, _V1, V2) -> V2.
-spec intersect_with(Combiner, Map1, Map2) -> Map3 when
Map1 :: #{Key => Value1},
@@ -90,35 +95,33 @@ intersect_combiner(_K, _V1, V2) ->
intersect_with(Combiner, Map1, Map2) when is_map(Map1),
is_map(Map2),
is_function(Combiner, 3) ->
- case map_size(Map1) < map_size(Map2) of
+ %% Use =< because we want to avoid reversing the combiner if we can
+ case map_size(Map1) =< map_size(Map2) of
true ->
- Iterator = maps:iterator(Map1),
- intersect_with_1(maps:next(Iterator),
- Map1,
- Map2,
- Combiner);
+ intersect_with_small_map_first(Combiner, Map1, Map2);
false ->
- Iterator = maps:iterator(Map2),
- intersect_with_1(maps:next(Iterator),
- Map2,
- Map1,
- fun(K, V1, V2) -> Combiner(K, V2, V1) end)
+ RCombiner = fun(K, V1, V2) -> Combiner(K, V2, V1) end,
+ intersect_with_small_map_first(RCombiner, Map2, Map1)
end;
intersect_with(Combiner, Map1, Map2) ->
error(error_type_merge_intersect(Map1, Map2, Combiner),
[Combiner, Map1, Map2]).
-intersect_with_1({K, V1, Iterator}, Map1, Map2, Combiner) ->
+intersect_with_small_map_first(Combiner, SmallMap, BigMap) ->
+ Next = maps:next(maps:iterator(SmallMap)),
+ intersect_with_iterate(Next, [], SmallMap, BigMap, Combiner).
+
+intersect_with_iterate({K, V1, Iterator}, Keep, Map1, Map2, Combiner) ->
+ Next = maps:next(Iterator),
case Map2 of
#{ K := V2 } ->
- NewMap1 = Map1#{ K := Combiner(K, V1, V2) },
- intersect_with_1(maps:next(Iterator), NewMap1, Map2, Combiner);
+ V = Combiner(K, V1, V2),
+ intersect_with_iterate(Next, [{K,V}|Keep], Map1, Map2, Combiner);
_ ->
- intersect_with_1(maps:next(Iterator), maps:remove(K, Map1), Map2, Combiner)
+ intersect_with_iterate(Next, Keep, Map1, Map2, Combiner)
end;
-intersect_with_1(none, Res, _, _) ->
- Res.
-
+intersect_with_iterate(none, Keep, _Map1, _Map2, _Combiner) ->
+ maps:from_list(Keep).
%% Shadowed by erl_bif_types: maps:is_key/2
-spec is_key(Key,Map) -> boolean() when
--
2.26.2