File 4361-erts-Optimize-maps-merge-2-of-small-maps.patch of Package erlang

From c684d1a7a295df555c5d1aef81ce566d92d573fe Mon Sep 17 00:00:00 2001
From: Sverker Eriksson <sverker@erlang.org>
Date: Thu, 9 Mar 2023 17:54:24 +0100
Subject: [PATCH] erts: Optimize maps:merge/2 of small maps

If 2nd map has all keys return it as-is.
If 1st map has all keys reuse its key tuple.
---
 erts/emulator/beam/erl_map.c         | 55 ++++++++++++++++------------
 erts/emulator/test/map_SUITE.erl     | 39 +++++++++++++++++---
 system/doc/efficiency_guide/maps.xml | 20 ++++++++--
 3 files changed, 81 insertions(+), 33 deletions(-)

diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index c4ddee1436..4cd9d65839 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -1279,34 +1279,31 @@ BIF_RETTYPE maps_merge_2(BIF_ALIST_2) {
     BIF_ERROR(BIF_P, BADMAP);
 }
 
-static Eterm flatmap_merge(Process *p, Eterm nodeA, Eterm nodeB) {
+static Eterm flatmap_merge(Process *p, Eterm map1, Eterm map2) {
     Eterm *hp,*thp;
-    Eterm tup;
     Eterm *ks,*vs,*ks1,*vs1,*ks2,*vs2;
     flatmap_t *mp1,*mp2,*mp_new;
     Uint n,n1,n2,i1,i2,need,unused_size=0;
     Sint c = 0;
 
-    mp1  = (flatmap_t*)flatmap_val(nodeA);
-    mp2  = (flatmap_t*)flatmap_val(nodeB);
+    mp1  = (flatmap_t*)flatmap_val(map1);
+    mp2  = (flatmap_t*)flatmap_val(map2);
     n1   = flatmap_get_size(mp1);
     n2   = flatmap_get_size(mp2);
 
-    if (n1 == 0) return nodeB;
-    if (n2 == 0) return nodeA;
+    if (n1 == 0) return map2;
+    if (n2 == 0) return map1;
 
     need = MAP_HEADER_FLATMAP_SZ + 1 + 2 * (n1 + n2);
 
     hp     = HAlloc(p, need);
-    thp    = hp;
-    tup    = make_tuple(thp);
-    ks     = hp + 1; hp += 1 + n1 + n2;
     mp_new = (flatmap_t*)hp; hp += MAP_HEADER_FLATMAP_SZ;
     vs     = hp; hp += n1 + n2;
+    thp    = hp;
+    ks     = hp + 1; hp += 1 + n1 + n2;
 
     mp_new->thing_word = MAP_HEADER_FLATMAP;
-    mp_new->size = 0;
-    mp_new->keys = tup;
+    mp_new->keys = make_tuple(thp);
 
     i1  = 0; i2 = 0;
     ks1 = flatmap_get_keys(mp1);
@@ -1346,20 +1343,32 @@ static Eterm flatmap_merge(Process *p, Eterm nodeA, Eterm nodeB) {
 	i2++;
     }
 
-    if (unused_size) {
-	/* the key tuple is embedded in the heap, write a bignum to clear it.
-	 *
-	 * release values as normal since they are on the top of the heap
-	 * size = n1 + n1 - unused_size
-	 */
-
-	*ks = make_pos_bignum_header(unused_size - 1);
-	HRelease(p, vs + unused_size, vs);
-    }
-
     n = n1 + n2 - unused_size;
-    *thp = make_arityval(n);
     mp_new->size = n;
+    *thp = make_arityval(n);
+
+    if (unused_size ) {
+        Eterm* hp_release;
+
+        if (n == n2) {
+            /* Reuse entire map2 */
+            HRelease(p, hp, (Eterm *)mp_new);
+            return map2;
+        }
+        else if (n == n1) {
+            /* Reuse key tuple of map1 */
+            mp_new->keys = mp1->keys;
+            /* Release key tuple and unused values */
+            hp_release = thp - unused_size;
+        }
+        else {
+            /* Unused values are embedded in the heap, write bignum to clear them */
+            *vs = make_pos_bignum_header(unused_size - 1);
+            /* Release unused keys */
+            hp_release = ks;
+        }
+	HRelease(p, hp, hp_release);
+    }
 
     /* Reshape map to a hashmap if the map exceeds the limit */
 
diff --git a/erts/emulator/test/map_SUITE.erl b/erts/emulator/test/map_SUITE.erl
index 5707a8e56c..043a3f3260 100644
--- a/erts/emulator/test/map_SUITE.erl
+++ b/erts/emulator/test/map_SUITE.erl
@@ -1981,12 +1981,7 @@ t_bif_map_merge(Config) when is_list(Config) ->
 
     M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
 	4 => number, 18446744073709551629 => wat},
-
-    #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
-	4 := number, 18446744073709551629 := wat} = maps:merge(#{}, M0),
-
-    #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
-	4 := number, 18446744073709551629 := wat} = maps:merge(M0, #{}),
+    merge_with_empty(M0),
 
     M1 = #{ "hi" => "hello again", float => 3.3, {1,2} => "tuple", 4 => integer },
 
@@ -2001,6 +1996,7 @@ t_bif_map_merge(Config) when is_list(Config) ->
     Is = lists:seq(1,N),
     M2 = maps:from_list([{I,I}||I<-Is]),
     150000 = maps:size(M2),
+    merge_with_empty(M2),
     M3 = maps:from_list([{<<I:32>>,I}||I<-Is]),
     150000 = maps:size(M3),
     M4 = maps:merge(M2,M3),
@@ -2039,6 +2035,28 @@ t_bif_map_merge(Config) when is_list(Config) ->
     M11 = maps:merge(M9,M10),
     ok = check_keys_exist(Ks1 ++ Ks2, M11),
 
+    %% Verify map and/or key tuple reuse
+
+    MS = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>},
+    merge_with_empty(MS),
+    MS_keys = erts_internal:map_to_tuple_keys(MS),
+
+    %% key tuple reuse
+    MS_a = maps:merge(MS, #{int => 4}),
+    true = erts_debug:same(erts_internal:map_to_tuple_keys(MS_a), MS_keys),
+    %% map reuse
+    MS_b = maps:merge(#{int => 4}, MS),
+    true = erts_debug:same(MS_b, MS),
+
+    %% mutated map reuse with literal key tuple
+    MS_c = maps:put(int, 4, maps:remove(int, MS)),
+    false = erts_debug:same(erts_internal:map_to_tuple_keys(MS_c), MS_keys),
+    MS_cc = maps:merge(MS, MS_c),
+    true = erts_debug:same(MS_cc, MS_c),
+
+    MS_d = maps:merge(MS_c, MS),
+    true = erts_debug:same(MS_d, MS),
+
     %% error case
     do_badmap(fun(T) ->
 		      {'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
@@ -2050,6 +2068,15 @@ t_bif_map_merge(Config) when is_list(Config) ->
 	      end),
     ok.
 
+merge_with_empty(M0) ->
+    M0_1 = maps:merge(#{}, M0),
+    M0 = M0_1,
+    true = erts_debug:same(M0, M0_1),
+
+    M0_2 = maps:merge(M0, #{}),
+    M0 = M0_2,
+    true = erts_debug:same(M0, M0_2),
+    ok.
 
 t_bif_map_put(Config) when is_list(Config) ->
     M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
diff --git a/system/doc/efficiency_guide/maps.xml b/system/doc/efficiency_guide/maps.xml
index 140bdd549a..a2e004af59 100644
--- a/system/doc/efficiency_guide/maps.xml
+++ b/system/doc/efficiency_guide/maps.xml
@@ -37,6 +37,7 @@
   finally the functions in the <seeerl
   marker="stdlib:maps">maps</seeerl> module.</p>
 
+  <marker id="terminology"/>
   <p>Terminology used in this chapter:</p>
   <list type="bulleted">
     <item>A map with at most 32 elements will informally be called a
@@ -500,7 +501,8 @@ get(Key, Map, Default) ->
       <p>Therefore, a call <c>maps:get/3</c> is more expensive than a
       call to <c>maps:get/2</c>.</p>
 
-      <p>If a small map is used as alternative to using a record,
+      <p>If a <seeguide marker="#terminology">small map</seeguide>
+      is used as alternative to using a record,
       instead of calling <c>maps:get/3</c> multiple times to handle
       default values, consider putting the default values in a map and
       merging that map with the other map:</p>
@@ -580,7 +582,17 @@ get(Key, Map, Default) ->
     <section>
       <title>maps:merge/2</title>
       <p><seemfa marker="stdlib:maps#merge/2">maps:merge/2</seemfa>
-      is implemented in C.</p>
+      is implemented in C. For <seeguide marker="#terminology">small
+      maps</seeguide>, the key tuple may be shared with any of the argument
+      maps if that argument map contains all the keys. Literal key tuples are
+      prefered if possible.</p>
+      <note>
+	<p>
+	  The sharing of key tuples by <c>maps:merge/2</c> was introduced in
+	  OTP 26.0. Older versions always contructed a new key tuple on
+	  the callers heap.
+	</p>
+      </note>
     </section>
 
     <section>
@@ -613,7 +625,7 @@ get(Key, Map, Default) ->
       <p>If the keys are constants known at compile-time, using the
       map update syntax with the <c>=></c> operator is more
       efficient than multiple calls to <c>maps:put/3</c>,
-      especially for small maps.</p>
+      especially for <seeguide marker="#terminology">small maps</seeguide>.</p>
     </section>
 
     <section>
@@ -660,7 +672,7 @@ get(Key, Map, Default) ->
       <p>If the keys are constants known at compile-time, using the
       map update syntax with the <c>:=</c> operator is more
       efficient than multiple calls to <c>maps:update/3</c>,
-      especially for small maps.</p>
+      especially for <seeguide marker="#terminology">small maps</seeguide>.</p>
     </section>
 
     <section>
-- 
2.35.3

openSUSE Build Service is sponsored by