Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
5791-erts-Optimize-maps-merge-2-of-small-maps.p...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 5791-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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor