Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:25
erlang
1941-Optimize-the-copy-that-occurs-in-ets-looku...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 1941-Optimize-the-copy-that-occurs-in-ets-lookup_element.patch of Package erlang
From 04625c7082ae25d81cff663c6b16b8e92906e319 Mon Sep 17 00:00:00 2001 From: Robin Morisset <rmorisset@fb.com> Date: Thu, 1 Sep 2022 03:13:18 -0700 Subject: [PATCH] Optimize the copy that occurs in ets:lookup_element Michal Muskala pointed to me that sometimes ets:lookup_element can be slower than ets:lookup, even when the value is found. For example: ``` erlperf "ets:lookup(ac_tab, {env, kernel, logger})." "ets:lookup_element(ac_tab, {env, kernel, logger}, 2)." Code || QPS Time Rel ets:lookup(ac_tab, {env, kernel, logger}). 1 2859 Ki 349 ns 100% ets:lookup_element(ac_tab, {env, kernel, logger}, 2). 1 2164 Ki 462 ns 75% ``` The reason for this is that lookup can find the size of the allocation needed directly in the DbTerm, then copy the whole thing efficiently with copy_shallow_x, whereas lookup_element must first walk all over the value to be copied in size_object_x, and then walk it again in copy_struct_x. This patch fixes this in three steps: - Make db_store_term (used by ets:insert among others) use a specialized version of copy_struct that makes the fields be laid in order (instead of being chaotically interspersed as usual) - Finding the size of the needed allocation can now be done efficiently (see db_field_size) - And we can use copy_shallow_x for the copy like lookup (just with a small change so that it does not assume that it is copying a tuple). Result: ``` PATH=~/otp/bin:$PATH erlperf "ets:lookup(ac_tab, {env, kernel, logger})." "ets:lookup_element(ac_tab, {env, kernel, logger}, 2)." Code || QPS Time Rel ets:lookup_element(ac_tab, {env, kernel, logger}, 2). 1 2945 Ki 339 ns 100% ets:lookup(ac_tab, {env, kernel, logger}). 1 2860 Ki 349 ns 97% ``` I've tried measuring the potential impact on ets:insert performance by looking at the 2 throughput benchmarks in ets_SUITE. More specifically, I looked at the 50% insert/50% delete mix (since it maximizes the impact of insert) and at the 1 process case (since my change should not affect concurrency in any way). Here are the results for the long benchmark, with 3 runs in each configuration, listing all three kinds of table tested: [set, {write_concurrency, auto}, {read_concurrency, true}]: Baseline: 3.55 / 3.31 / 2.85 This patch: 3.20 / 3.08 / 3.44 [set, {write_concurrency, true}, {read_concurrency, true}]: Baseline: 3.31 / 3.11 / 2.78 This patch: 3.13 / 3.08 / 3.42 [ordered_set, {write_concurrency, true}, {read_concurrency, true}]: Baseline: 1.51 / 1.56 / 1.17 This patch: 1.50 / 1.37 / 1.59 (all running on my M1 MBP) There is no obvious performance regression, but the noise is sufficiently high that I may have missed some. Co-authored-by: Sverker Eriksson <sverker@erlang.org> --- erts/emulator/beam/copy.c | 52 ++++++-- erts/emulator/beam/erl_db_hash.c | 1 + erts/emulator/beam/erl_db_tree.c | 1 + erts/emulator/beam/erl_db_util.c | 218 +++++++++++++++++++++++++------ erts/emulator/beam/erl_db_util.h | 10 +- erts/emulator/beam/erl_term.h | 1 + erts/emulator/beam/global.h | 19 ++- lib/stdlib/test/ets_SUITE.erl | 21 ++- 8 files changed, 264 insertions(+), 59 deletions(-) diff --git a/erts/emulator/beam/copy.c b/erts/emulator/beam/copy.c index 79f90fbcb0..a22317f6c1 100644 --- a/erts/emulator/beam/copy.c +++ b/erts/emulator/beam/copy.c @@ -619,7 +619,6 @@ cleanup: return sum; } - /* * Copy a structure to a heap. */ @@ -1932,19 +1931,52 @@ all_clean: * pointers are offsetted to point correctly in the new location. * * Typically used to copy a term from an ets table. + */ +Eterm copy_shallow_obj_x(Eterm obj, Uint sz, Eterm **hpp, ErlOffHeap *off_heap +#ifdef ERTS_COPY_REGISTER_LOCATION + , + char *file, int line +#endif +) { + Eterm *source_ptr; + Eterm *target_ptr; + + if (sz == 0) { + ASSERT(is_zero_sized(obj)); + return obj; + } + + ASSERT(is_boxed(obj) || is_list(obj)); + ASSERT(!is_zero_sized(obj)); + + source_ptr = ptr_val(obj); +#ifdef ERTS_COPY_REGISTER_LOCATION + target_ptr = copy_shallow_x(source_ptr, sz, hpp, off_heap, file, line); +#else + target_ptr = copy_shallow_x(source_ptr, sz, hpp, off_heap); +#endif + + return is_boxed(obj) ? make_boxed(target_ptr) : make_list(target_ptr); +} + + +/* + * Copy a term that is guaranteed to be contained in a single + * heap block. The heap block is copied word by word, and any + * pointers are offsetted to point correctly in the new location. * - * NOTE: Assumes that term is a tuple (ptr is an untagged tuple ptr). + * Typically used to copy a term from an ets table. */ -Eterm -copy_shallow_x(Eterm* ERTS_RESTRICT ptr, Uint sz, Eterm** hpp, ErlOffHeap* off_heap +Eterm* copy_shallow_x(Eterm *ERTS_RESTRICT ptr, Uint sz, Eterm **hpp, + ErlOffHeap *off_heap #ifdef ERTS_COPY_REGISTER_LOCATION - , char *file, int line + , + char *file, int line #endif - ) -{ - Eterm* tp = ptr; - Eterm* hp = *hpp; - const Eterm res = make_tuple(hp); +) { + Eterm *tp = ptr; + Eterm *hp = *hpp; + Eterm* res = hp; const Sint offs = (hp - tp) * sizeof(Eterm); const Eterm empty_tuple_literal = ERTS_GLOBAL_LIT_EMPTY_TUPLE; diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c index aa04a6a03c..cec2504574 100644 --- a/erts/emulator/beam/erl_db_hash.c +++ b/erts/emulator/beam/erl_db_hash.c @@ -3877,6 +3877,7 @@ Ldone: handle->flags = flags; handle->new_size = b->dbterm.size; handle->u.hash.lck_ctr = lck_ctr; + handle->old_tpl = NULL; return 1; } diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c index 23b28e47f8..02306195ac 100644 --- a/erts/emulator/beam/erl_db_tree.c +++ b/erts/emulator/beam/erl_db_tree.c @@ -3446,6 +3446,7 @@ int db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root, handle->flags = flags; handle->bp = (void**) pp; handle->new_size = (*pp)->dbterm.size; + handle->old_tpl = NULL; return 1; } diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c index 8243ac1a73..8419756d40 100644 --- a/erts/emulator/beam/erl_db_util.c +++ b/erts/emulator/beam/erl_db_util.c @@ -2495,7 +2495,8 @@ restart: top = HAllocX(build_proc, sz, HEAP_XTRA); if (in_flags & ERTS_PAM_CONTIGUOUS_TUPLE) { ASSERT(is_tuple(term)); - *esp++ = copy_shallow(tuple_val(term), sz, &top, &MSO(build_proc)); + *esp++ = make_tuple(copy_shallow(tuple_val(term), sz, &top, + &MSO(build_proc))); } else { *esp++ = copy_struct(term, sz, &top, &MSO(build_proc)); @@ -3125,9 +3126,27 @@ both_size_set: handle->new_size = handle->new_size - oldval_sz + newval_sz; - /* write new value in old dbterm, finalize will make a flat copy */ + /* + * Write new value in old dbterm, finalize will make a flat copy. + */ + if (!(handle->flags & DB_MUST_RESIZE)) { + const size_t nbytes = (arityval(handle->dbterm->tpl[0]) + 1) * sizeof(Eterm); + /* + * First time here. Save the original tuple array in order to make + * fast size calculations of untouched elements. + */ + ASSERT(!handle->tb->common.compress); + ASSERT(!handle->old_tpl); + if (nbytes > sizeof(handle->old_tpl_dflt)) { + handle->old_tpl = erts_alloc(ERTS_ALC_T_TMP, nbytes); + } else { + handle->old_tpl = handle->old_tpl_dflt; + } + sys_memcpy(handle->old_tpl, handle->dbterm->tpl, nbytes); + handle->flags |= DB_MUST_RESIZE; + } + ASSERT(!!handle->old_tpl != !!handle->tb->common.compress); handle->dbterm->tpl[position] = newval; - handle->flags |= DB_MUST_RESIZE; } static ERTS_INLINE byte* db_realloc_term(DbTableCommon* tb, void* old, @@ -3281,6 +3300,38 @@ static void* copy_to_comp(int keypos, Eterm obj, DbTerm* dest, return top.cp; } +static ERTS_INLINE +Eterm copy_ets_element(Eterm obj, int sz, Eterm **hpp, ErlOffHeap *off_heap) +{ +#ifdef DEBUG + const Eterm* const hp_start = *hpp; +#endif + Eterm copy; + + if (sz == 0) { + ASSERT(is_immed(obj) || obj == ERTS_GLOBAL_LIT_EMPTY_TUPLE); + return obj; + } + ASSERT(is_not_immed(obj)); + + if (is_list(obj) && is_immed(CAR(list_val(obj)))) { + /* copy_struct() would put this last, + but we need the top term to be first in block */ + Eterm* src = list_val(obj); + Eterm* dst = *hpp; + + CAR(dst) = CAR(src); + *hpp += 2; + CDR(dst) = copy_struct(CDR(src), sz-2, hpp, off_heap); + copy = make_list(dst); + } + else { + copy = copy_struct(obj, sz, hpp, off_heap); + } + ASSERT(ptr_val(copy) == hp_start); + return copy; +} + /* ** Copy the object into a possibly new DbTerm, ** offset is the offset of the DbTerm from the start @@ -3293,8 +3344,24 @@ void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj) byte* basep; DbTerm* newp; Eterm* top; - int size = size_object(obj); + Eterm* source_ptr; + Eterm* dest_ptr; + int arity, i, size; ErlOffHeap tmp_offheap; + Uint elem_sizes_dflt[8]; + Uint* elem_sizes = elem_sizes_dflt; + + /* Calculate sizes of all elements and total size */ + source_ptr = tuple_val(obj); + arity = arityval(*source_ptr); + if (arity > sizeof(elem_sizes_dflt) / sizeof(elem_sizes_dflt[0])) { + elem_sizes = erts_alloc(ERTS_ALC_T_TMP, arity * sizeof(*elem_sizes)); + } + size = arity + 1; + for (i = 0; i < arity; i++) { + elem_sizes[i] = size_object(source_ptr[i+1]); + size += elem_sizes[i]; + } if (old != 0) { basep = ((byte*) old) - offset; @@ -3317,14 +3384,26 @@ void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj) (offset + sizeof(DbTerm) + sizeof(Eterm)*(size-1))); newp = (DbTerm*) (basep + offset); } + + /* + * Do the actual copy. Lay out elements in order after the top tuple. + * This is relied upon by db_copy_element_from_ets. + */ newp->size = size; top = newp->tpl; - tmp_offheap.first = NULL; - copy_struct(obj, size, &top, &tmp_offheap); + tmp_offheap.first = NULL; + *top++ = *source_ptr++; // copy the header + dest_ptr = top + arity; + for (i = 0; i < arity; ++i) { + *top++ = copy_ets_element(source_ptr[i], elem_sizes[i], &dest_ptr, + &tmp_offheap); + } newp->first_oh = tmp_offheap.first; #ifdef DEBUG_CLONE newp->debug_clone = NULL; #endif + if (elem_sizes != elem_sizes_dflt) + erts_free(ERTS_ALC_T_TMP, elem_sizes); return basep; } @@ -3367,6 +3446,7 @@ void* db_store_term_comp(DbTableCommon *tb, /* May be NULL */ return basep; } +static Uint db_element_size(DbTerm *obj, Eterm* tpl, Uint pos); void db_finalize_resize(DbUpdateHandle* handle, Uint offset) { @@ -3379,6 +3459,8 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset) byte* newp = erts_db_alloc(ERTS_ALC_T_DB_TERM, tbl, alloc_sz); byte* oldp = *(handle->bp); + ASSERT(handle->flags & DB_MUST_RESIZE); + sys_memcpy(newp, oldp, offset); /* copy only hash/tree header */ *(handle->bp) = newp; newDbTerm = (DbTerm*) (newp + offset); @@ -3396,16 +3478,36 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset) } else { ErlOffHeap tmp_offheap; - Eterm* tpl = handle->dbterm->tpl; - Eterm* top = newDbTerm->tpl; + DbTerm* src = handle->dbterm; + const Uint arity = arityval(src->tpl[0]); + Eterm* top = &newDbTerm->tpl[arity+1]; + int i; + + ASSERT(handle->old_tpl); tmp_offheap.first = NULL; + newDbTerm->tpl[0] = src->tpl[0]; + for (i = 1; i <= arity; ++i) { + Uint sz; + if (is_immed(src->tpl[i])) { + newDbTerm->tpl[i] = src->tpl[i]; + } + else { + if (src->tpl[i] != handle->old_tpl[i]) { + sz = size_object(src->tpl[i]); + } + else { + sz = db_element_size(src, handle->old_tpl, i); + } + newDbTerm->tpl[i] = copy_ets_element(src->tpl[i], sz, &top, + &tmp_offheap); + } + } + ASSERT((byte*)top == (newp + alloc_sz)); + newDbTerm->first_oh = tmp_offheap.first; - { - copy_struct(make_tuple(tpl), handle->new_size, &top, &tmp_offheap); - newDbTerm->first_oh = tmp_offheap.first; - ASSERT((byte*)top == (newp + alloc_sz)); - } + if (handle->old_tpl != handle->old_tpl_dflt) + erts_free(ERTS_ALC_T_TMP, handle->old_tpl); } } @@ -3446,36 +3548,78 @@ Eterm db_copy_from_comp(DbTableCommon* tb, DbTerm* bp, Eterm** hpp, return make_tuple(hp); } -Eterm db_copy_element_from_ets(DbTableCommon* tb, Process* p, - DbTerm* obj, Uint pos, - Eterm** hpp, Uint extra) -{ +Eterm db_copy_element_from_ets(DbTableCommon *tb, Process *p, DbTerm *obj, + Uint pos, Eterm **hpp, Uint extra) { if (is_immed(obj->tpl[pos])) { - *hpp = HAlloc(p, extra); - return obj->tpl[pos]; - } - if (tb->compress && pos != tb->keypos) { - byte* ext = elem2ext(obj->tpl, pos); - Sint sz = erts_decode_ext_size_ets(ext, db_alloced_size_comp(obj)) + extra; - Eterm copy; - ErtsHeapFactory factory; - - erts_factory_proc_prealloc_init(&factory, p, sz); - copy = erts_decode_ext_ets(&factory, ext); - *hpp = erts_produce_heap(&factory, extra, 0); - erts_factory_close(&factory); + *hpp = HAlloc(p, extra); + return obj->tpl[pos]; + } + if (tb->compress) { + if (pos == tb->keypos) { + Uint sz = size_object(obj->tpl[pos]); + *hpp = HAlloc(p, sz + extra); + return copy_struct(obj->tpl[pos], sz, hpp, &MSO(p)); + } + else { + byte *ext = elem2ext(obj->tpl, pos); + Sint sz = + erts_decode_ext_size_ets(ext, db_alloced_size_comp(obj)) + extra; + Eterm copy; + ErtsHeapFactory factory; + + erts_factory_proc_prealloc_init(&factory, p, sz); + copy = erts_decode_ext_ets(&factory, ext); + *hpp = erts_produce_heap(&factory, extra, 0); + erts_factory_close(&factory); #ifdef DEBUG_CLONE - ASSERT(EQ(copy, obj->debug_clone[pos])); + ASSERT(EQ(copy, obj->debug_clone[pos])); #endif - return copy; - } - else { - Uint sz = size_object(obj->tpl[pos]); - *hpp = HAlloc(p, sz + extra); - return copy_struct(obj->tpl[pos], sz, hpp, &MSO(p)); + return copy; + } + } else { + Uint sz = db_element_size(obj, obj->tpl, pos); + *hpp = HAlloc(p, sz + extra); + return copy_shallow_obj(obj->tpl[pos], sz, hpp, &MSO(p)); } } +/* + * Return the size of an element of an uncompressed ETS record. + * Relies on each element of the ETS record being laid out contiguously, + * and starting with the top term. + */ +static Uint db_element_size(DbTerm *obj, Eterm* tpl, Uint pos) { + Eterm *start_ptr; + Eterm *end_ptr; + Eterm elem; + Uint arity, i, sz; + + elem = tpl[pos]; + if (is_zero_sized(elem)) + return 0; + + ASSERT(is_boxed(elem) || is_list(elem)); + start_ptr = ptr_val(elem); + ASSERT(!erts_is_literal(elem, start_ptr)); + + arity = arityval(tpl[0]); + for (i = pos + 1; i <= arity; ++i) { + elem = tpl[i]; + if (!is_zero_sized(elem)) { + ASSERT(is_boxed(elem) || is_list(elem)); + end_ptr = ptr_val(elem); + ASSERT(!erts_is_literal(elem, end_ptr)); + goto done; + } + } + end_ptr = obj->tpl + obj->size; + +done: + sz = end_ptr - start_ptr; + ASSERT(sz == size_object(tpl[pos])); + return sz; + +} /* Our own "cleanup_offheap" * as ProcBin and ErtsMRefThing may be unaligned in compressed terms diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h index 3be89781c0..bcc3a864ea 100644 --- a/erts/emulator/beam/erl_db_util.h +++ b/erts/emulator/beam/erl_db_util.h @@ -101,6 +101,12 @@ typedef struct { int current_level; } catree; } u; + Eterm* old_tpl; +#ifdef DEBUG + Eterm old_tpl_dflt[2]; +#else + Eterm old_tpl_dflt[8]; +#endif } DbUpdateHandle; /* How safe are we from double-hits or missed objects @@ -404,10 +410,10 @@ ERTS_GLB_INLINE Eterm db_copy_object_from_ets(DbTableCommon* tb, DbTerm* bp, Eterm** hpp, ErlOffHeap* off_heap) { if (tb->compress) { - return db_copy_from_comp(tb, bp, hpp, off_heap); + return db_copy_from_comp(tb, bp, hpp, off_heap); } else { - return copy_shallow(bp->tpl, bp->size, hpp, off_heap); + return make_tuple(copy_shallow(bp->tpl, bp->size, hpp, off_heap)); } } diff --git a/erts/emulator/beam/erl_term.h b/erts/emulator/beam/erl_term.h index 5ec1d885e1..7bcbda0b85 100644 --- a/erts/emulator/beam/erl_term.h +++ b/erts/emulator/beam/erl_term.h @@ -179,6 +179,7 @@ struct erl_node_; /* Declared in erl_node_tables.h */ #endif #define is_not_both_immed(x,y) (!is_both_immed((x),(y))) +#define is_zero_sized(x) (is_immed(x) || (x) == ERTS_GLOBAL_LIT_EMPTY_TUPLE) /* boxed object access methods */ diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index 8c4dd90966..aee6236b5b 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -1142,10 +1142,10 @@ Uint size_shared(Eterm); #ifdef ERTS_COPY_REGISTER_LOCATION -#define copy_shared_perform(U, V, X, Y, Z) \ - copy_shared_perform_x((U), (V), (X), (Y), (Z), __FILE__, __LINE__) Eterm copy_shared_perform_x(Eterm, Uint, erts_shcopy_t*, Eterm**, ErlOffHeap*, char *file, int line); +#define copy_shared_perform(U, V, X, Y, Z) \ + copy_shared_perform_x((U), (V), (X), (Y), (Z), __FILE__, __LINE__) Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_t*, char *file, int line); @@ -1154,16 +1154,21 @@ Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_ #define copy_struct_litopt(Obj,Sz,HPP,OH,LitArea) \ copy_struct_x(Obj,Sz,HPP,OH,NULL,LitArea,__FILE__,__LINE__) +Eterm* copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*, + char *file, int line); #define copy_shallow(R, SZ, HPP, OH) \ copy_shallow_x((R), (SZ), (HPP), (OH), __FILE__, __LINE__) -Eterm copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*, + +Eterm copy_shallow_obj_x(Eterm, Uint, Eterm**, ErlOffHeap*, char *file, int line); +#define copy_shallow_obj(R, SZ, HPP, OH) \ + copy_shallow_obj_x((R), (SZ), (HPP), (OH), __FILE__, __LINE__) #else +Eterm copy_shared_perform_x(Eterm, Uint, erts_shcopy_t*, Eterm**, ErlOffHeap*); #define copy_shared_perform(U, V, X, Y, Z) \ copy_shared_perform_x((U), (V), (X), (Y), (Z)) -Eterm copy_shared_perform_x(Eterm, Uint, erts_shcopy_t*, Eterm**, ErlOffHeap*); Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_t*); #define copy_struct(Obj,Sz,HPP,OH) \ @@ -1171,9 +1176,13 @@ Eterm copy_struct_x(Eterm, Uint, Eterm**, ErlOffHeap*, Uint*, erts_literal_area_ #define copy_struct_litopt(Obj,Sz,HPP,OH,LitArea) \ copy_struct_x(Obj,Sz,HPP,OH,NULL,LitArea) +Eterm* copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*); #define copy_shallow(R, SZ, HPP, OH) \ copy_shallow_x((R), (SZ), (HPP), (OH)) -Eterm copy_shallow_x(Eterm* ERTS_RESTRICT, Uint, Eterm**, ErlOffHeap*); + +Eterm copy_shallow_obj_x(Eterm, Uint, Eterm**, ErlOffHeap*); +#define copy_shallow_obj(R, SZ, HPP, OH) \ + copy_shallow_obj_x((R), (SZ), (HPP), (OH)) #endif diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index 64009a5273..605f301d05 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -2301,13 +2301,19 @@ update_element_do(Tab,Tuple,Key,UpdPos) -> Big32 = 16#12345678, Big64 = 16#123456789abcdef0, - Values = { 623, -27, 0, Big32, -Big32, Big64, -Big64, Big32*Big32, + RefcBin = list_to_binary(lists:seq(1,100)), + BigMap1 = maps:from_list([{N,N} || N <- lists:seq(1,33)]), + BigMap2 = BigMap1#{key => RefcBin, RefcBin => value}, + Values = { 623, -27, Big32, -Big32, Big64, -Big64, Big32*Big32, -Big32*Big32, Big32*Big64, -Big32*Big64, Big64*Big64, -Big64*Big64, "A", "Sverker", [], {12,-132}, {}, - <<45,232,0,12,133>>, <<234,12,23>>, list_to_binary(lists:seq(1,100)), + <<45,232,0,12,133>>, <<234,12,23>>, RefcBin, (fun(X) -> X*Big32 end), - make_ref(), make_ref(), self(), ok, update_element, 28, 29 }, - Length = size(Values), + make_ref(), make_ref(), self(), ok, update_element, + #{a => value, "hello" => "world", 1.0 => RefcBin }, + BigMap1, BigMap2}, + Length = tuple_size(Values), + 29 = Length, PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) -> NextIx = (ToIx+Rand) rem Length, @@ -2327,7 +2333,12 @@ update_element_do(Tab,Tuple,Key,UpdPos) -> true = ets:update_element(Tab, Key, PosValArg), ArgHash = erlang:phash2({Tab,Key,PosValArg}), NewTuple = update_tuple(PosValArg,Tuple), - [NewTuple] = ets:lookup(Tab,Key) + [NewTuple] = ets:lookup(Tab,Key), + [begin + Elem = element(I, NewTuple), + Elem = ets:lookup_element(Tab, Key, I) + end + || I <- lists:seq(1, tuple_size(NewTuple))] end, LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length -> -- 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