This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Better optimise array and hash assignment
authorDavid Mitchell <davem@iabyn.com>
Wed, 5 Oct 2016 09:10:56 +0000 (10:10 +0100)
committerDavid Mitchell <davem@iabyn.com>
Wed, 26 Oct 2016 07:37:27 +0000 (08:37 +0100)
commit8b0c3377906a6f991cd6c21a674bf9561d85e3cb
tree35ad50fcfe94ef6e3dc9bb1ae2c84bb807ff76d0
parentbeb8db25b082984390ac72ef4a50bf8ec7fdffdb
Better optimise array and hash assignment

[perl #127999] Slowdown in split + list assign

Re-implement the code that handles e.g.

    (..., @a) = (...);
    (..., %h) = (...);

to make it a lot faster - more than reversing a performance regression
introduced in 5.24.0 - and fix some bugs. In particular, it now
special-cases an empty RHS, which just clears the aggregate, e.g.

    (..., @a) = ()

Getting list assignment correct is quite tricky, due to the possibility of
premature frees, like @a = ($a[0]), and magic/tied values on the LHS or
RHS being triggered too soon/late, which might have side-effects.  This
often requires making a copy of each RHS element (and indeed for assigning
to an array or hash, the values need copying anyway). But copying too soon
can result in leaked SVs if magic (such as calling FETCH()) dies. This
usually involves mortalising all the copies, which slows things down.

Further, a bug fix in 5.24.0 added the SV_NOSTEAL flag when copying SVs.
This meant in something like @a = (split(...))[0,0], where the two SvTEMPs
on the RHS are the same, the first copy is no longer allowed to steal the
PVX buffer, which would have made the second SV undef. But this means that
PVX buffers are now always copied, which resulted in the slowdown seen in
RT #127999.

Amongst the general rewriting and optimising, this commit does the
following specific things to boost performance (and fix RT #127999).

* If the SVs on the RHS are non-magical SvTEMPs with a ref count of 1, then
the SV isn't copied; instead it is stored directly in the array/hash. This
more than undoes the cost of SV_NOSTEAL.

* The tmps stack is now used as a temporary refcounted version of the
argument stack frame, meaning that args placed there will be freed on
croak.  In something like @a = (....), each RHS element is copied, with
the copy placed on the temps stack. Then @a is cleared. Then the elements
on the tmps stack are stored in the array, and removed from the temps
stack (with the ownership of 1 reference count transferring from the temps
stack to the array). Normally by the time pp_aassign() returns, there is
nothing left on the tmps stack and tmps_free() isn't called - this is the
novel element that distinguishes this from the normal use of mortalising.

* For hash assignment, the keys and values are processed in separate
loops, with keys not normally being copied.

* The ENTER/SAVEFREESV(ary/hash)/LEAVE has been removed, and the array or
hash kept temporarily alive by using the temps stack along with all the
other copied SVs.

* The main 'for each LHS element' loop has been split into two loops: the
second one is run when there no more RHS elements to consume. The second
loop is much simpler, and makes things like @a = () much faster.

Here are the average expr::aassign:: benchmarks for selected perls
(raw numbers - lower is better)

          5.6.1    5.22.0    5.24.0    5.25.5      this
         ------    ------    ------    ------    ------
    Ir   1355.9    1497.8    1387.0    1382.0    1146.6
    Dr    417.2     454.2     410.1     411.1     335.2
    Dw    260.6     270.8     249.0     246.8     194.5
  COND    193.5     223.2     212.0     207.7     174.4
   IND     25.3      17.6      10.8      10.8      10.0

COND_m      4.1       3.1       3.1       3.7       2.8
 IND_m      8.9       6.1       5.5       5.5       5.5

And this code:

    my @a;
    for my $i (1..10_000_000) {
        @a = (1,2,3);
        #@a = ();
    }

with the empty assign is 33% faster than blead, and without is 12% faster
than blead.
pp_hot.c
t/op/aassign.t
t/op/hash.t
t/op/tie.t
t/op/tiearray.t
t/perf/benchmarks