Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
4371-Add-lookup_element-4-which-takes-a-default...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 4371-Add-lookup_element-4-which-takes-a-default-value.patch of Package erlang
From e528e26bf76267db4ff450d598c9a816d8d4a4ce Mon Sep 17 00:00:00 2001 From: Robin Morisset <rmorisset@fb.com> Date: Mon, 22 Aug 2022 12:10:26 +0200 Subject: [PATCH] Add lookup_element/4, which takes a default value Originally proposed in https://erlangforums.com/t/proposal-adding-ets-lookup-element-as-list-3/1743/4. lookup_element/3 tends to be faster than lookup/2 when the key is always found, and is also a bit more ergonomic. Unfortunately, when the key is absent, it throws an exception which is both somewhat slow and less ergonomic to catch than matching the [] returned by lookup. LeonardB suggested that a simple solution would be to offer an alternate version of lookup_element that takes an extra argument, and returns that argument if the key is not found. This matches several other functions in the standard library such as maps:get and proplists:get_value. I confirmed the theory with some very simple microbenchmark: https://gist.github.com/RobinMorisset/62030c8fcb7376e16291904a9098e342 Code || QPS Time Rel bench_lookup:bench_lookup_element_default_present(). 1 133 Ki 7538 ns 100% bench_lookup:bench_lookup_element_present(). 1 131 Ki 7604 ns 99% bench_lookup:bench_lookup_present(). 1 89464 11177 ns 67% bench_lookup:bench_lookup_element_default_absent(). 1 140 Ki 7156 ns 100% bench_lookup:bench_lookup_absent(). 1 134 Ki 7477 ns 95% bench_lookup:bench_lookup_element_absent(). 1 88187 11339 ns 63% As expected, lookup_element/4 is as fast as lookup_element/3 when the element is present, and as fast as lookup/2 when it is absent. I've updated ets_SUITE.erl, and also done some manual testing. Finally there is the microbenchmark above. --- erts/emulator/beam/bif.tab | 1 + erts/emulator/beam/erl_db.c | 40 ++++++++++++++++++++++++-- lib/stdlib/doc/src/ets.xml | 26 +++++++++++++++++ lib/stdlib/src/erl_stdlib_errors.erl | 3 ++ lib/stdlib/src/ets.erl | 12 +++++++- lib/stdlib/test/ets_SUITE.erl | 42 ++++++++++++++++++++++++++-- 6 files changed, 119 insertions(+), 5 deletions(-) diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 5f9c8624ec..500eee4735 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -362,6 +362,7 @@ bif ets:first/1 bif ets:is_compiled_ms/1 bif ets:lookup/2 bif ets:lookup_element/3 +bif ets:lookup_element/4 bif ets:info/1 bif ets:info/2 bif ets:last/1 diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index bdcb136a05..d243e0506e 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -2670,8 +2670,44 @@ BIF_RETTYPE ets_lookup_element_3(BIF_ALIST_3) } } -/* - * BIF to erase a whole table and release all memory it holds +/* +** Get an element from a term +** get_element_4(Tab, Key, Index, Default) +** return the element or a list of elements if bag or Default if the element is not present +*/ +BIF_RETTYPE ets_lookup_element_4(BIF_ALIST_4) +{ + DbTable* tb; + Sint index; + int cret; + Eterm ret; + + CHECK_TABLES(); + + DB_BIF_GET_TABLE(tb, DB_READ, LCK_READ, BIF_ets_lookup_element_4); + + if (is_not_small(BIF_ARG_3) || ((index = signed_val(BIF_ARG_3)) < 1)) { + db_unlock(tb, LCK_READ); + BIF_ERROR(BIF_P, BADARG); + } + + cret = tb->common.meth->db_get_element(BIF_P, tb, + BIF_ARG_2, index, &ret); + db_unlock(tb, LCK_READ); + switch (cret) { + case DB_ERROR_NONE: + BIF_RET(ret); + case DB_ERROR_BADKEY: + BIF_RET(BIF_ARG_4); + case DB_ERROR_SYSRES: + BIF_ERROR(BIF_P, SYSTEM_LIMIT); + default: + BIF_ERROR(BIF_P, BADARG); + } +} + +/* + * BIF to erase a whole table and release all memory it holds */ BIF_RETTYPE ets_delete_1(BIF_ALIST_1) { diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml index 507d27d8d6..e323713668 100644 --- a/lib/stdlib/doc/src/ets.xml +++ b/lib/stdlib/doc/src/ets.xml @@ -955,6 +955,32 @@ Error: fun containing local Erlang function calls </desc> </func> + <func> + <name name="lookup_element" arity="4" since="OTP 26.0"/> + <fsummary>Return the <c>Pos</c>:th element of all objects with a + specified key in an ETS table, or <c>Default</c> if there is no such object.</fsummary> + <desc> + <p>For a table <c><anno>Table</anno></c> of type <c>set</c> or + <c>ordered_set</c>, the function returns the + <c><anno>Pos</anno></c>:th + element of the object with key <c><anno>Key</anno></c>.</p> + <p>For tables of type <c>bag</c> or <c>duplicate_bag</c>, + the functions returns a list with the <c><anno>Pos</anno></c>:th + element of every object with key <c><anno>Key</anno></c>.</p> + <p>If no object with key <c><anno>Key</anno></c> exists, the + function returns <c><anno>Default</anno></c>.</p> + <p>If <c><anno>Pos</anno></c> is larger than the size of + any tuple with a matching key, the function exits with + reason <c>badarg</c>.</p> + <p>The difference between <c>set</c>, <c>bag</c>, and + <c>duplicate_bag</c> on one hand, and <c>ordered_set</c> on + the other, regarding the fact that <c>ordered_set</c> + view keys as equal when they <em>compare equal</em> + whereas the other table types regard them equal only when + they <em>match</em>, holds for <c>lookup_element/4</c>.</p> + </desc> + </func> + <func> <name name="match" arity="1" since=""/> <fsummary>Continues matching objects in an ETS table.</fsummary> diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl index 3298ee0f95..748a57e66a 100644 --- a/lib/stdlib/src/erl_stdlib_errors.erl +++ b/lib/stdlib/src/erl_stdlib_errors.erl @@ -641,6 +641,9 @@ format_ets_error(lookup_element, [_,_,Pos]=Args, Cause) -> [TabCause, "", PosCause] end end; +format_ets_error(lookup_element, [Tab, Key, Pos, _Default], Cause) -> + % The default argument cannot cause an error. + format_ets_error(lookup_element, [Tab, Key, Pos], Cause); format_ets_error(match, [_], _Cause) -> [bad_continuation]; format_ets_error(match, [_,_,_]=Args, Cause) -> diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl index 979a75b231..3545c8a186 100644 --- a/lib/stdlib/src/ets.erl +++ b/lib/stdlib/src/ets.erl @@ -70,7 +70,7 @@ -export([all/0, delete/1, delete/2, delete_all_objects/1, delete_object/2, first/1, give_away/3, info/1, info/2, insert/2, insert_new/2, is_compiled_ms/1, last/1, lookup/2, - lookup_element/3, match/1, match/2, match/3, match_object/1, + lookup_element/3, lookup_element/4, match/1, match/2, match/3, match_object/1, match_object/2, match_object/3, match_spec_compile/1, match_spec_run_r/3, member/2, new/2, next/2, prev/2, rename/2, safe_fixtable/2, select/1, select/2, select/3, @@ -232,6 +232,16 @@ lookup(_, _) -> lookup_element(_, _, _) -> erlang:nif_error(undef). +-spec lookup_element(Table, Key, Pos, Default) -> Elem when + Table :: table(), + Key :: term(), + Pos :: pos_integer(), + Default :: term(), + Elem :: term() | [term()]. + +lookup_element(_, _, _, _) -> + erlang:nif_error(undef). + -spec match(Table, Pattern) -> [Match] when Table :: table(), Pattern :: match_pattern(), diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index 64009a5273..6a58ff14f0 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -39,7 +39,7 @@ -export([tab2file/1, tab2file2/1, tabfile_ext1/1, tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1, badfile/1]). -export([heavy_lookup/1, heavy_lookup_element/1, heavy_concurrent/1]). --export([lookup_element_mult/1]). +-export([lookup_element_mult/1, lookup_element_default/1]). -export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]). -export([t_delete_object/1, t_init_table/1, t_whitebox/1, select_bound_chunk/1, t_delete_all_objects/1, t_test_ms/1, @@ -191,7 +191,7 @@ groups() -> privacy]}, {insert, [], [empty, badinsert]}, {lookup, [], [badlookup, lookup_order]}, - {lookup_element, [], [lookup_element_mult]}, + {lookup_element, [], [lookup_element_mult, lookup_element_default]}, {delete, [], [delete_elem, delete_tab, delete_large_tab, delete_large_named_table, evil_delete, table_leak, @@ -4034,6 +4034,41 @@ fill_tab(Tab,Val) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lookup_element_default(Config) when is_list(Config) -> + EtsMem = etsmem(), + + TabSet = ets_new(foo, [set]), + ets:insert(TabSet, {key, 42}), + 42 = ets:lookup_element(TabSet, key, 2, 13), + 13 = ets:lookup_element(TabSet, not_key, 2, 13), + {'EXIT',{badarg,_}} = catch ets:lookup_element(TabSet, key, 3, 13), + true = ets:delete(TabSet), + + TabOrderedSet = ets_new(foo, [ordered_set]), + ets:insert(TabOrderedSet, {key, 42}), + 42 = ets:lookup_element(TabOrderedSet, key, 2, 13), + 13 = ets:lookup_element(TabOrderedSet, not_key, 2, 13), + {'EXIT',{badarg,_}} = catch ets:lookup_element(TabOrderedSet, key, 3, 13), + true = ets:delete(TabOrderedSet), + + TabBag = ets_new(foo, [bag]), + ets:insert(TabBag, {key, 42}), + ets:insert(TabBag, {key, 43, 44}), + [42, 43] = ets:lookup_element(TabBag, key, 2, 13), + 13 = ets:lookup_element(TabBag, not_key, 2, 13), + {'EXIT',{badarg,_}} = catch ets:lookup_element(TabBag, key, 3, 13), + true = ets:delete(TabBag), + + TabDuplicateBag = ets_new(foo, [duplicate_bag]), + ets:insert(TabDuplicateBag, {key, 42}), + ets:insert(TabDuplicateBag, {key, 42}), + ets:insert(TabDuplicateBag, {key, 43, 44}), + [42, 42, 43] = ets:lookup_element(TabDuplicateBag, key, 2, 13), + 13 = ets:lookup_element(TabDuplicateBag, not_key, 2, 13), + {'EXIT',{badarg,_}} = catch ets:lookup_element(TabDuplicateBag, key, 3, 13), + true = ets:delete(TabDuplicateBag), + + verify_etsmem(EtsMem). %% OTP-2386. Multiple return elements. lookup_element_mult(Config) when is_list(Config) -> @@ -8890,6 +8925,9 @@ error_info(_Config) -> {lookup_element, [OneKeyTab, one, 4]}, + {lookup_element, ['$Tab', no_key, 1, default_value], [no_fail]}, + {lookup_element, [OneKeyTab, one, 4, default_value]}, + {match, [bad_continuation], [no_table]}, {match, ['$Tab', <<1,2,3>>], [no_fail]}, -- 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