🔬47. Push-all optimisation of List.roll in Perl 6

🔬47. Push-all optimisation of List.roll in Raku

N. B. Perl 6 has been renamed to Raku. Click to read more.


Last evening, I made a commit based on my recent observation. Let me devote today’s post to that.

In the last few days, we were talking about the two methods for getting random elements from a list — pick and roll. When you pass an integer to the methods, both of them internally use an instance of the class implementing the Iterator role. Depending on the situation, either pull-one or push-all method is called on that object.

Just as a reminder, here’s the skeleton of the two methods from src/core/List.pm (the samples are not working code):

multi method roll(\number) {
    Seq.new(class :: does Iterator {
        method pull-one() is raw {
        }
    }.new(self,number.Int))
}


multi method pick(List:D: $number is copy) {
    Seq.new(class :: does Iterator {
        method pull-one() {
        }
        method push-all($target) {
        }
    }.new(self,$elems,$number))
}

The problem is that in the case of roll, Rakudo calls pull-one for each element of the list, while in the case of pick, it just gets the whole list at one go.

In this program, both methods are using pull-one:

say <a b c d e>.roll(4);
say <a b c d e>.pick(4);

Although if you change it, only pick switches to push-one, while roll makes as many pull-one calls as you need to fetch all the requested elements individually:

my @r = <a b c d e>.roll(4);
say @r;

my @p = <a b c d e>.pick(4);
say @p;

What I did, I added the push-all method to the roll method:

method push-all($target --> IterationEnd) {
    nqp::while(
        $!todo,
        nqp::stmts(
            ($target.push(nqp::atpos($!list,$!elems.rand.floor))),
            ($!todo = $!todo - 1)
        )
    )
}

Now, in the above example, pick is also using the push-all method.

Compare the speed of the program before and after the change:

$ time ./perl6 -e'my @a = "a".."z"; for ^500_000 {my @b = @a.roll(20)}'
real	0m26.321s
user	0m26.010s
sys	0m0.163s


$ time ./perl6 -e'my @a = "a".."z"; for ^500_000 {my @b = @a.roll(20)}'
real	0m20.829s
user	0m20.701s
sys	0m0.130s

With the given data, it works 20% faster. Add some more code and gain some speed 🙂

P. S. Also read Zoffix’s post (or its part) in the Rakudo.party blog with more information about the push-all method.

4 thoughts on “🔬47. Push-all optimisation of List.roll in Perl 6”

Leave a Reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code