Skip to content

Commit

Permalink
Make .rotate return a Seq, rather than a List, saves memory
Browse files Browse the repository at this point in the history
- Add R:It.ReifiedRotate to produce rotated values from a reified list
- Adapt List/Shaped1Array.rotate to use new iterator
- Add Array.rotate to use new iterator, making sure it uses right descriptor
- Add R:It.RotateIterator to produce rotated values from an iterator
- Add Seq.rotate to use this iterator
- Remove R:In.RotateListToList, it is no longer needed

This change is mostly about memory usage, so will be most visible when
applying .rotate on large lists / arrays.  Instead of basically creating a
clone of the list / array with the values in the right order, it produces
the values on the fly, preventing a potentially costly copy.

This also makes sure that .rotate produces containers with the right
descriptor for list/arrays with holes in them.

The Seq.rotate case does not need reification at all if the rotation value
is greater than 0.

This requires some tweaking in roast, as there are some tests assuming that
.rotate always returns a list / array, rather than a Seq.  This is currently
also documented that way, which should also change.

This change makes .rotate also to be in line with .reverse, which seems
sensible to me.

Finally, done this in one giant commit, so that it can be easily reverted
should that be necessary.
  • Loading branch information
lizmat committed May 7, 2020
1 parent 4a3f29d commit 4b501bd
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 45 deletions.
8 changes: 8 additions & 0 deletions src/core.c/Array.pm6
Expand Up @@ -427,6 +427,14 @@ my class Array { # declared in BOOTSTRAP
!! Rakudo::Iterator.Empty
}

method rotate(List:D: Int(Cool) $rotate = 1 --> Seq:D) is nodal {
self.is-lazy # reifies
?? Failure.new(X::Cannot::Lazy.new(:action<rotate>))
!! Seq.new: nqp::getattr(self,List,'$!reified')
?? Rakudo::Iterator.ReifiedRotate($rotate, self, $!descriptor)
!! Rakudo::Iterator.Empty
}

multi method List(Array:D: :$view --> List:D) {
nqp::if(
self.is-lazy, # can't make a List
Expand Down
23 changes: 6 additions & 17 deletions src/core.c/List.pm6
Expand Up @@ -1136,23 +1136,12 @@ my class List does Iterable does Positional { # declared in BOOTSTRAP
!! Rakudo::Iterator.Empty
}

method rotate(List:D: Int(Cool) $rotate = 1) is nodal {
nqp::if(
self.is-lazy, # reifies
Failure.new(X::Cannot::Lazy.new(:action<rotate>)),
nqp::if(
$!reified,
Rakudo::Internals.RotateListToList(
self, $rotate,
nqp::p6bindattrinvres(nqp::create(self),List,'$!reified',
nqp::setelems(
nqp::create(IterationBuffer),nqp::elems($!reified)
)
)
),
nqp::create(self)
)
)
method rotate(List:D: Int(Cool) $rotate = 1 --> Seq:D) is nodal {
self.is-lazy # reifies
?? Failure.new(X::Cannot::Lazy.new(:action<rotate>))
!! Seq.new: $!reified
?? Rakudo::Iterator.ReifiedRotate($rotate, self, Mu)
!! Rakudo::Iterator.Empty
}

proto method combinations(|) is nodal {*}
Expand Down
24 changes: 0 additions & 24 deletions src/core.c/Rakudo/Internals.pm6
Expand Up @@ -60,30 +60,6 @@ my class Rakudo::Internals {
method dynamic() { False }
}

# rotate nqp list to another given list without using push/pop
method RotateListToList(\from,\n,\to) {
nqp::stmts(
(my $from := nqp::getattr(from,List,'$!reified')),
nqp::if((my int $elems = nqp::elems($from)),
nqp::stmts(
(my $to := nqp::getattr(to,List,'$!reified')),
(my int $i = -1),
(my int $j = nqp::mod_i(nqp::sub_i(nqp::sub_i($elems,1),n),$elems)),
nqp::if(nqp::islt_i($j,0),($j = nqp::add_i($j,$elems))),
nqp::while(
nqp::islt_i(($i = nqp::add_i($i,1)),$elems),
nqp::bindpos(
$to,
($j = nqp::mod_i(nqp::add_i($j,1),$elems)),
nqp::atpos($from,$i)
),
),
),
),
to
)
}

method RANGE-AS-ints ($range, $exception) {
# Convert a Range to min/max values that can fit into an `int`
# Treats values smaller than int.Range.min as int.Range.min
Expand Down
131 changes: 131 additions & 0 deletions src/core.c/Rakudo/Iterator.pm6
Expand Up @@ -2981,6 +2981,92 @@ class Rakudo::Iterator {
ReifiedReverseIterator.new(list, descriptor)
}

# Return an iterator for a List/Array that has been completely reified,
# or an IterationBuffer, that will produce values in according to a given
# rotation value. Takes a descriptor to create correct values for holes
# in the List/Array, use Mu to have holes returned as Nil.
my class ReifiedRotateIterator does PredictiveIterator {
has $!reified;
has $!descriptor;
has int $!todo;
has int $!i;

method !SET-SELF(int $rotate, \list, Mu \descriptor) {
$!reified := nqp::istype(list,List)
?? nqp::getattr(list,List,'$!reified')
!! list;
$!descriptor := nqp::eqaddr(descriptor,Mu)
?? nqp::null()
!! descriptor;

nqp::if(
($!todo = my int $elems = nqp::elems($!reified)),
nqp::stmts(
($!i = nqp::sub_i(nqp::mod_i(
nqp::add_i(nqp::mod_i($rotate,$elems),$elems),
$elems
),1)),
self
),
Rakudo::Iterator.Empty
)
}
method new(\rotate, \list, Mu \des) {
nqp::create(self)!SET-SELF(rotate, list, des)
}

method !hole(int $i) is raw {
nqp::isnull($!descriptor)
?? Nil
!! nqp::p6scalarfromdesc(
ContainerDescriptor::BindArrayPos.new(
$!descriptor, $!reified, $i
)
)
}

method pull-one() is raw {
nqp::isge_i(($!todo = nqp::sub_i($!todo,1)),0)
?? nqp::ifnull(
nqp::atpos(
$!reified,
($!i = nqp::mod_i(nqp::add_i($!i,1),nqp::elems($!reified)))
),
self!hole($!i)
)
!! IterationEnd
}
method push-all(\target --> IterationEnd) {
my $reified := $!reified; # lexicals are faster than attributes
my int $elems = nqp::elems($reified);
my int $todo = $!todo;
my int $i = $!i;

nqp::while( # doesn't sink
nqp::isge_i(($todo = nqp::sub_i($todo,1)),0),
target.push(
nqp::ifnull(
nqp::atpos(
$reified,
($i = nqp::mod_i(nqp::add_i($i,1),$elems))
),
self!hole($i)
)
)
);
$!todo = $todo;
}
method skip-one() {
$!i = nqp::mod_i(nqp::add_i($!i,1),nqp::elems($!reified));
nqp::isge_i(($!todo = nqp::sub_i($!todo,1)),0)
}
method count-only(--> Int:D) { $!todo + nqp::islt_i($!todo,0) }
method sink-all(--> IterationEnd) { $!todo = -1 }
}
method ReifiedRotate(\rotate, \list, Mu \descriptor) {
ReifiedRotateIterator.new(rotate, list, descriptor)
}

# Return a lazy iterator that will repeat the values of a given
# source iterator indefinitely. Even when given a lazy iterator,
# it will cache the values seen to handle case that the iterator
Expand Down Expand Up @@ -3097,6 +3183,51 @@ class Rakudo::Iterator {
RepeatLoop.new(&body, &cond, $label)
}

# Return an iterator for a non-lazy iterator that rotates values for a
# given positive amount. This will cache the given amount, and produce
# them at the end after the given iterator is exhausted.
my class RotateIterator does Iterator {
has $!iterator;
has $!rotated;

method !SET-SELF(int $rotate, \iterator) {

# set up values to be rotated
my $rotated := nqp::create(IterationBuffer);
nqp::until(
nqp::iseq_i(nqp::elems($rotated),$rotate),
nqp::if(
nqp::eqaddr((my \pulled := iterator.pull-one),IterationEnd),
(return ReifiedRotateIterator.new($rotate, $rotated, Mu)),
nqp::push($rotated,pulled)
)
);

$!iterator := iterator;
$!rotated := $rotated;
self
}
method new(\rot, \iter) { nqp::create(self)!SET-SELF(rot, iter) }

method !exhausted() is raw {
$!iterator := nqp::null;
nqp::shift($!rotated)
}

method pull-one() is raw {
nqp::isnull($!iterator)
?? nqp::elems($!rotated)
?? nqp::shift($!rotated)
!! IterationEnd
!! nqp::eqaddr((my \pulled := $!iterator.pull-one),IterationEnd)
?? self!exhausted()
!! pulled
}
}
method Rotate(\rotate, \iterator) {
RotateIterator.new(rotate, iterator)
}

# Return an iterator that rotorizes the given iterator with the
# given cycle. If the cycle is a Cool, then it is assumed to
# be a single Int value to R:It.Batch with. Otherwise it is
Expand Down
21 changes: 20 additions & 1 deletion src/core.c/Seq.pm6
Expand Up @@ -110,7 +110,7 @@ my class Seq is Cool does Iterable does Sequence {
)
}

method reverse(--> Seq:D) {
method reverse(--> Seq:D) is nodal {
nqp::if(
(my $iterator := self.iterator).is-lazy,
Failure.new(X::Cannot::Lazy.new(:action<reverse>)),
Expand All @@ -121,6 +121,25 @@ my class Seq is Cool does Iterable does Sequence {
)
}

method rotate(Int(Cool) $rotate = 1 --> Seq:D) is nodal {
nqp::if(
(my $iterator := self.iterator).is-lazy,
Failure.new(X::Cannot::Lazy.new(:action<rotate>)),
nqp::if(
$rotate,
Seq.new( nqp::if(
$rotate > 0,
Rakudo::Iterator.Rotate($rotate, $iterator),
nqp::stmts(
$iterator.push-all(my \buffer := nqp::create(IterationBuffer)),
Rakudo::Iterator.ReifiedRotate($rotate, buffer, Mu)
)
)),
self
)
)
}

method sink(--> Nil) {
nqp::if(
nqp::isconcrete($!iter),
Expand Down
7 changes: 4 additions & 3 deletions src/core.c/Shaped1Array.pm6
Expand Up @@ -254,9 +254,10 @@
Rakudo::Iterator.Empty
))
}
method rotate(::?CLASS:D: Int(Cool) $rotate = 1) is nodal {
Rakudo::Internals.RotateListToList(
self, $rotate, self.new(:shape(self.shape)))
method rotate(::?CLASS:D: Int(Cool) $rotate = 1 --> Seq:D) is nodal {
Seq.new: Rakudo::Iterator.ReifiedRotate(
$rotate, self, nqp::getattr(self,Array,'$!descriptor')
)
}
method sum() is nodal { self.List::sum }
}
Expand Down

0 comments on commit 4b501bd

Please sign in to comment.