Skip to content

Commit

Permalink
Re-imagine xxxmap, makes >>. about 2x as fast
Browse files Browse the repository at this point in the history
This commit completely re-imagines the nodemap / deepmap / duckmap
functionality, makes them about 2x as fast with about 1/8 of the
memory churn (in a benchmark of `my @A = ^1000; @A>>.++ for ^1000`)
So what does this do:

- move implementation of nodemap / deepmap / duckmap as methods of
  Any to Any-iterable (as they probably should have been during the
  GLR, but I guess it worked the way it did so it was left then).
- the nodemap / deepmap / duckmap subs are now frontends for the
  the associated methods
- the HYPERxxx subs now call the methods instead of the subs
- the nodemap implementation no longer randomizes the order in which
  the mapping occurs.  I don't see this as a property of nodemap:
  the fact that it was implemented like that, was that hypering
  callables that are nodal, calls nodemap().  And to make sure that
  code doesn't depend on the *order* in which hypering is performed,
  it would randomize that to some extent.  Oddly enough, when hypering
  a callable that is *not* nodal, deepmap() would be called, which was
  never randomized.
- this is spectest clean, but this only shows that we have no tests
  for the randomization of hypering.
  • Loading branch information
lizmat committed Feb 21, 2021
1 parent 6b83135 commit 412d770
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 169 deletions.
138 changes: 138 additions & 0 deletions src/core.c/Any-iterable-methods.pm6
Expand Up @@ -2041,6 +2041,135 @@ Consider using a block if any of these are necessary for your mapping code."
multi method rotor(Any:D: +@cycle, :$partial) {
Seq.new(Rakudo::Iterator.Rotor(self.iterator,@cycle,$partial))
}

proto method nodemap(|) is nodal {*}
multi method nodemap(Associative:D: &op) {
self.new.STORE: self.keys, self.values.nodemap(&op)
}
multi method nodemap(&op) {
my \iterator := self.iterator;
return Failure.new(X::Cannot::Lazy.new(:action<nodemap>))
if iterator.is-lazy;

my \buffer := nqp::create(IterationBuffer);
my $value := iterator.pull-one;

nqp::until(
nqp::eqaddr($value,IterationEnd),
nqp::stmts(
(my int $redo = 1),
nqp::while(
$redo,
nqp::stmts(
$redo = 0,
nqp::handle(
nqp::push(buffer,op($value)),
'REDO', ($redo = 1),
'LAST', ($value := IterationEnd),
)
),
:nohandler
),
($value := iterator.pull-one)
)
);
buffer.List
}

proto method deepmap(|) is nodal {*}
multi method deepmap(Associative:D: &op) {
self.new.STORE: self.keys, self.values.deepmap(&op)
}
multi method deepmap(&op) {
my \iterator := self.iterator;
my \buffer := nqp::create(IterationBuffer);
my $value := iterator.pull-one;

nqp::until(
nqp::eqaddr($value,IterationEnd),
nqp::stmts(
(my int $redo = 1),
nqp::while(
$redo,
nqp::stmts(
$redo = 0,
nqp::handle(
nqp::stmts(
(my $result := nqp::if(
nqp::istype($value,Iterable) && $value.DEFINITE,
$value.deepmap(&op),
op($value)
)),
nqp::if(
nqp::istype($result,Slip),
$result.iterator.push-all(buffer),
nqp::push(buffer,$result)
),
),
'REDO', ($redo = 1),
'LAST', ($value := IterationEnd),
)
),
:nohandler
),
($value := iterator.pull-one)
)
);
nqp::p6bindattrinvres(
nqp::if(nqp::istype(self,List),self,List).new, # keep subtypes of List
List,'$!reified',buffer
)
}

proto method duckmap(|) is nodal {*}
multi method duckmap(Associative:D: &op) {
self.new.STORE: self.keys, self.values.duckmap(&op)
}
multi method duckmap(&op) {
my \iterator := self.iterator;
my \buffer := nqp::create(IterationBuffer);
my $value := iterator.pull-one;

sub duck(\arg) {
CATCH {
return nqp::istype(arg,Iterable:D)
?? arg.duckmap(&op)
!! arg
}
op(arg)
}

nqp::until(
nqp::eqaddr($value,IterationEnd),
nqp::stmts(
(my int $redo = 1),
nqp::while(
$redo,
nqp::stmts(
$redo = 0,
nqp::handle(
nqp::stmts(
(my $result := duck($value)),
nqp::if(
nqp::istype($result,Slip),
$result.iterator.push-all(buffer),
nqp::push(buffer,$result)
),
),
'REDO', ($redo = 1),
'LAST', ($value := IterationEnd),
)
),
:nohandler
),
($value := iterator.pull-one)
)
);
nqp::p6bindattrinvres(
nqp::if(nqp::istype(self,List),self,List).new, # keep subtypes of List
List,'$!reified',buffer
)
}
}

BEGIN Attribute.^compose;
Expand Down Expand Up @@ -2134,4 +2263,13 @@ multi sub sort(&by, +values) { values.sort(&by) }
multi sub sort(@values) { @values.sort }
multi sub sort(+values) { values.sort }

proto sub nodemap($, $, *%) {*}
multi sub nodemap(&op, \obj) { obj.nodemap(&op) }

proto sub deepmap($, $, *%) {*}
multi sub deepmap(&op, \obj) { obj.deepmap(&op) }

proto sub duckmap($, $, *%) {*}
multi sub duckmap(&op, \obj) { obj.duckmap(&op) }

# vim: expandtab shiftwidth=4
5 changes: 0 additions & 5 deletions src/core.c/Any.pm6
Expand Up @@ -162,11 +162,6 @@ my class Any { # declared in BOOTSTRAP
method permutations(|c) is nodal { self.list.permutations(|c) }
method join($separator = '') is nodal { self.list.join($separator) }

# XXX GLR should move these
method nodemap(&block) is nodal { nodemap(&block, self) }
method duckmap(&block) is nodal { duckmap(&block, self) }
method deepmap(&block) is nodal { deepmap(&block, self) }

# XXX GLR Do we need tree post-GLR?
proto method tree(|) is nodal {*}
multi method tree(Any:U:) { self }
Expand Down
89 changes: 0 additions & 89 deletions src/core.c/Rakudo/Internals.pm6
Expand Up @@ -1572,95 +1572,6 @@ implementation detail and has no serviceable parts inside"
$target
}

proto method coremap(|) {*}

multi method coremap(\op, Associative \h, Bool :$deep) {
my @keys = h.keys;
hash @keys Z self.coremap(op, h{@keys}, :$deep)
}

my class CoreMap does Rakudo::SlippyIterator {
has &!block;
has $!source;
has $!deep;

method new(&block, $source, $deep) {
my \iter := nqp::create(self);
nqp::bindattr(iter, self, '&!block', &block);
nqp::bindattr(iter, self, '$!source', $source);
nqp::bindattr(iter, self, '$!deep', $deep);
iter
}

method is-lazy() {
$!source.is-lazy
}

method pull-one() is raw {
my int $redo = 1;
nqp::if(
$!slipping && nqp::not_i(nqp::eqaddr((my $result := self.slip-one),IterationEnd)),
$result,
nqp::if(
nqp::eqaddr((my $value := $!source.pull-one),IterationEnd),
$value,
nqp::stmts(
nqp::while(
$redo,
nqp::stmts(
$redo = 0,
nqp::handle(
nqp::stmts(
nqp::if(
$!deep,
nqp::if(
nqp::istype($value, Iterable) && $value.DEFINITE,
($result := Rakudo::Internals.coremap(&!block, $value, :$!deep).item),
($result := &!block($value))
),
($result := &!block($value))
),
nqp::if(
nqp::istype($result, Slip),
nqp::stmts(
($result := self.start-slip($result)),
nqp::if(
nqp::eqaddr($result, IterationEnd),
nqp::stmts(
($value := $!source.pull-one()),
($redo = 1 unless nqp::eqaddr($value, IterationEnd))
))
))
),
'NEXT', nqp::stmts(
($value := $!source.pull-one()),
nqp::eqaddr($value, IterationEnd)
?? ($result := IterationEnd)
!! ($redo = 1)),
'REDO', $redo = 1,
'LAST', ($result := IterationEnd))),
:nohandler
),
$result
)
)
)
}
}
multi method coremap(\op, \obj, Bool :$deep) {
my \iterable := obj.DEFINITE && nqp::istype(obj, Iterable)
?? obj
!! obj.list;

my \result := CoreMap.new(op, iterable.iterator, $deep);
my \type := nqp::istype(obj, List) ?? obj !! List; # keep subtypes of List
my \buffer := nqp::create(IterationBuffer);
result.push-all(buffer);
my \retval := type.new;
nqp::bindattr(retval, List, '$!reified', buffer);
nqp::iscont(obj) ?? retval.item !! retval;
}

method INFIX_COMMA_SLIP_HELPER(\reified, \future) {
my $list :=
nqp::p6bindattrinvres(nqp::create(List),List,'$!reified',reified);
Expand Down
90 changes: 15 additions & 75 deletions src/core.c/metaops.pm6
Expand Up @@ -486,96 +486,36 @@ sub METAOP_HYPER(\op, *%opt) is implementation-detail {
}

proto sub METAOP_HYPER_POSTFIX(|) is implementation-detail {*}
multi sub METAOP_HYPER_POSTFIX(\op) {
nqp::can(op,"nodal")
?? (-> \obj { nodemap(op, obj) })
!! (-> \obj { deepmap(op, obj) })
multi sub METAOP_HYPER_POSTFIX(&op) {
nqp::can(&op,"nodal") ?? *.nodemap(&op) !! *.deepmap(&op)
}

# no indirection for subscripts and such
proto sub METAOP_HYPER_POSTFIX_ARGS(|) is implementation-detail {*}
multi sub METAOP_HYPER_POSTFIX_ARGS(\obj,\op) {
nqp::can(op,"nodal")
?? nodemap(op, obj)
!! deepmap(op, obj)
multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, &op) {
nqp::can(&op,"nodal") ?? obj.nodemap(&op) !! obj.deepmap(&op)
}
multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, @args, \op) {
nqp::can(op,"nodal")
?? nodemap( -> \o { op.(o,@args) }, obj )
!! deepmap( -> \o { op.(o,@args) }, obj )
multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, @args, &op) {
nqp::can(&op,"nodal")
?? obj.nodemap(-> \o { op(o, @args) })
!! obj.deepmap(-> \o { op(o, @args) })
}
multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, \args, \op) {
nqp::can(op,"nodal")
?? nodemap( -> \o { op.(o,|args) }, obj )
!! deepmap( -> \o { op.(o,|args) }, obj )
multi sub METAOP_HYPER_POSTFIX_ARGS(\obj, \args, &op) {
nqp::can(&op,"nodal")
?? obj.nodemap( -> \o { op(o,|args) })
!! obj.deepmap( -> \o { op(o,|args) })
}

sub METAOP_HYPER_PREFIX(\op) is implementation-detail {
nqp::can(op,"nodal") # rarely true for prefixes
?? (-> \obj { nodemap(op, obj) })
!! (-> \obj { deepmap(op, obj) })
sub METAOP_HYPER_PREFIX(&op) is implementation-detail {
nqp::can(&op,"nodal") ?? *.nodemap(&op) !! *.deepmap(&op)
}

sub METAOP_HYPER_CALL(\list, |args) is implementation-detail {
deepmap(-> $c { $c(|args) }, list)
list.deepmap(-> &code { code(|args) })
}

sub HYPER(\operator, :$dwim-left, :$dwim-right, |c) is implementation-detail {
Hyper.new(operator, :$dwim-left, :$dwim-right).infix(|c)
}

proto sub deepmap($, $, *%) {*}
multi sub deepmap(\op, \obj) {
Rakudo::Internals.coremap(op, obj, :deep)
}
multi sub deepmap(\op, Associative \h) {
my @keys = h.keys;
hash @keys Z deepmap(op, h{@keys})
}

proto sub nodemap($, $, *%) {*}
multi sub nodemap(\op, \obj) {
my Mu $rpa := nqp::create(IterationBuffer);
my \objs := obj.list;
# as a wanted side-effect is-lazy reifies the list
fail X::Cannot::Lazy.new(:action<nodemap>) if objs.is-lazy;
return () unless my Mu $items := nqp::getattr(objs, List, '$!reified');
my Mu $o;
# We process the elements in two passes, end to start, to
# prevent users from relying on a sequential ordering of hyper.
# Also, starting at the end pre-allocates $rpa for us.
my int $i = nqp::elems($items) - 1;
nqp::while(
nqp::isge_i($i, 0),
nqp::stmts(
nqp::bindpos($rpa, $i, op.(nqp::atpos($items, $i))),
$i = nqp::sub_i($i, 2)
)
);
$i = nqp::elems($items) - 2;
nqp::while(
nqp::isge_i($i, 0),
nqp::stmts(
nqp::bindpos($rpa, $i, op.(nqp::atpos($items, $i))),
$i = nqp::sub_i($i, 2)
)
);
nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', $rpa)
}

multi sub nodemap(\op, Associative \h) {
my @keys = h.keys;
hash @keys Z nodemap(op, h{@keys})
}

proto sub duckmap($, $, *%) {*}
multi sub duckmap(\op, \obj) {
Rakudo::Internals.coremap(sub (\arg) { CATCH { return arg ~~ Iterable:D ?? duckmap(op,arg) !! arg }; op.(arg); }, obj);
}

multi sub duckmap(\op, Associative \h) {
my @keys = h.keys;
hash @keys Z duckmap(op, h{@keys})
}

# vim: expandtab shiftwidth=4

0 comments on commit 412d770

Please sign in to comment.