Skip to content

Commit

Permalink
pp_aassign: optimise: store NULLs in L elem slots
Browse files Browse the repository at this point in the history
(This commit only affects code within PERL_RC_STACK ifdefs)

In a list assignment such as (L1, L2, ...) = (R1, R2, ...),
pp_assign is called with the stack looking like:

    * R1 R2 ... * L1 L2 ...

Previously, all elements on the stack were popped at the end. This
commit changes it so that after each L element is processed, it is freed
and a NULL (or sometimes &PL_sv_undef) is stored at that slot in the
stack. Then at the end, PL_stack_sp can just be set to one slot below the
ex-L1, without having to run a loop  popping each element off the stack.
This reduces the number of memory reads and branches, at the cost of
more writes.

(All the R elems will still need to be freed, though).
  • Loading branch information
iabyn committed Nov 16, 2023
1 parent 3eb2167 commit 53fb2f6
Showing 1 changed file with 88 additions and 9 deletions.
97 changes: 88 additions & 9 deletions pp_hot.c
Expand Up @@ -2636,6 +2636,14 @@ PP(pp_aassign)
bool is_list = (gimme == G_LIST);
relem = firstrelem;
lelem = firstlelem;
#ifdef PERL_RC_STACK
/* Where we can reset stack to at the end, without needing to free
* each element. This is normally all the lelem's, but it can vary for
* things like odd number of hash elements, which pushes a
* &PL_sv_undef into the 'lvalue' part of the stack.
*/
SV ** first_discard = firstlelem;
#endif

if (relem > lastrelem)
goto no_relems;
Expand All @@ -2650,9 +2658,6 @@ PP(pp_aassign)
assert(relem <= lastrelem);
if (UNLIKELY(!lsv)) {
alias = TRUE;
/* remove NULL so that the faster rpp_popfree_to_NN() can be
* used on return */
lelem[-1] = &PL_sv_undef;
lsv = *lelem++;
ASSUME(SvTYPE(lsv) == SVt_PVAV);
}
Expand Down Expand Up @@ -2890,9 +2895,12 @@ PP(pp_aassign)
if (UNLIKELY(PL_delaymagic & DM_ARRAY_ISA))
/* its assumed @ISA set magic can't die and leak ary */
SvSETMAGIC(MUTABLE_SV(ary));
#ifndef PERL_RC_STACK
SvREFCNT_dec_NN(ary);

#ifdef PERL_RC_STACK
assert(lelem[-1] == (SV*)ary);
lelem[-1] = NULL;
#endif
SvREFCNT_dec_NN(ary);
relem = lastrelem + 1;
goto no_relems;
}
Expand All @@ -2907,6 +2915,21 @@ PP(pp_aassign)
if (UNLIKELY(nelems & 1)) {
do_oddball(lastrelem, relem);
/* we have firstlelem to reuse, it's not needed any more */
#ifdef PERL_RC_STACK
if (lelem - 1 == lastrelem + 1) {
/* the lelem slot we want to use is the
* one keeping hash alive. Mortalise the hash
* so it doesn't leak */
assert(lastrelem[1] == (SV*)hash);
sv_2mortal((SV*)hash);
}
else {
/* safe to repurpose old lelem slot */
assert(!lastrelem[1] || SvIMMORTAL(lastrelem[1]));
}
first_discard++;
assert(first_discard = lastrelem + 2);
#endif
*++lastrelem = &PL_sv_undef;
nelems++;
}
Expand Down Expand Up @@ -3125,7 +3148,17 @@ PP(pp_aassign)
}
}

#ifndef PERL_RC_STACK
#ifdef PERL_RC_STACK
/* Disarm the ref-counted pointer on the stack. This will
* usually point to the hash, except for the case of an odd
* number of elems where the hash was mortalised and its slot
* on the stack was made part of the relems with the slot's
* value overwritten with &PL_sv_undef. */
if (lelem[-1] == (SV*)hash) {
lelem[-1] = NULL;
SvREFCNT_dec_NN(hash);
}
#else
if (dirty_tmps) {
/* there are still some 'live' recounts on the tmps stack
* - usually caused by storing into a tied hash. So let
Expand Down Expand Up @@ -3185,9 +3218,13 @@ PP(pp_aassign)
#endif

sv_setsv(lsv, *relem);
SvSETMAGIC(lsv);
if (UNLIKELY(is_list))
rpp_replace_at_NN(relem, lsv);
SvSETMAGIC(lsv);
#ifdef PERL_RC_STACK
lelem[-1] = NULL;
SvREFCNT_dec_NN(lsv);
#endif
}
if (++relem > lastrelem)
goto no_relems;
Expand Down Expand Up @@ -3228,10 +3265,29 @@ PP(pp_aassign)
sv_set_undef(lsv);
SvSETMAGIC(lsv);
}
if (UNLIKELY(is_list))
rpp_replace_at_NN(relem++, lsv);
if (UNLIKELY(is_list)) {
/* this usually grows the list of relems to be returned
* into the stack space holding lelems (unless
* there was previously a hash with dup elements) */
#ifdef PERL_RC_STACK
assert(relem <= first_discard);
assert(relem < lelem);
if (relem == first_discard)
first_discard++;
#endif
rpp_replace_at(relem++, lsv);
#ifdef PERL_RC_STACK
if (relem == lelem)
/* skip the NULLing of the slot */
continue;
#endif
}
break;
} /* switch */
#ifdef PERL_RC_STACK
lelem[-1] = NULL;
SvREFCNT_dec_NN(lsv);
#endif
} /* while */

TAINT_NOT; /* result of list assign isn't tainted */
Expand Down Expand Up @@ -3321,7 +3377,30 @@ PP(pp_aassign)
}
PL_delaymagic = old_delaymagic;

#ifdef PERL_RC_STACK
/* On ref-counted builds, the code above should have stored
* NULL in each lelem field and already freed each lelem. Thus
* the popfree_to() can start at a lower point.
* Under some circumstances, &PL_sv_undef might be stored rather than
* NULL, but this also doesn't need its refcount decrementing.
* Assert that this is true.
* Note that duplicate hash keys in list context can cause
* lastrelem and relem to be lower than at the start;
* while an odd number of hash elements can cause lastrelem to
* have a value one higher than at the start */
# ifdef DEBUGGING
for (SV **svp = first_discard; svp <= PL_stack_sp; svp++)
assert(!*svp || SvIMMORTAL(*svp));
# endif
PL_stack_sp = first_discard - 1;

/* now pop all the R elements too */
rpp_popfree_to_NN((is_list ? relem : firstrelem) - 1);

#else
/* pop all L and R elements apart from any being returned */
rpp_popfree_to_NN((is_list ? relem : firstrelem) - 1);
#endif

if (gimme == G_SCALAR) {
rpp_extend(1);
Expand Down

0 comments on commit 53fb2f6

Please sign in to comment.