Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a Capture like a Slurpy

Tags:

capture

raku

I've been reading about Captures and this paragraph intrigued me:

Inside a Signature, a Capture may be created by prefixing a sigilless parameter with a vertical bar |. This packs the remainder of the argument list into that parameter.

This sounds a lot like a **@ (non flattening) slurpy, so this concocted this test code:

my $limit=1_000_000;
my @a=1 xx 10000;
my @b=-1 xx 10000;

sub test1(|c){
    1;
};

sub test2(**@c){
    1;
};
{ 
    for ^$limit {
        test1(@b,@a);
    }
    say now - ENTER now;
}
{
    for ^$limit {
        test2(@b,@a);
    }
    say now - ENTER now;
}

A sample run gives durations of each test block:

0.82560328                                                                                                                                                                                                                                                                                                         
2.6650674 

The Capture certainly seems to have the performance advantage. Is there a down side to using a Capture as a slurpy in this fashion? Have I over simplified the comparison?

like image 849
drclaw Avatar asked Apr 16 '19 13:04

drclaw


1 Answers

A Capture has two slots, holding a VM-level array (positional arguments) and hash (named arguments). It is quite cheaply constructed, and - since |c style arguments are quite common in various bits of the internals - has been well optimized. Since a capture parameter slurps up both positional and named arguments, any named arguments will be silently ignored. That's probably not much of an issue for methods, where unrequired named arguments will be silently placed into %_ anyway, but might be a consideration if using this construct on a sub, since it's not a pure optimization: it changes behavior.

The **@c case allocates an Array, and then allocates a Scalar container for each of the passed values, placing them into the Scalar containers and those Scalar containers into the Array. That's a reasonable amount of extra work.

There's another case not considered here, which is this one:

sub test3(**@c is raw){
    1;
}

That places a List in @c, and sets its elements to refer directly to the things that were passed. This is a bit cheaper than the case without is raw. In theory, it could probably perform as well as - if not better than - a capture parameter like |c; it probably just needs somebody working on the compiler to have a dig into why it doesn't yet.

In summary, if not caring about enforcing itemization and/or having a mutable Array of incoming arguments, then adding is raw is probably a better optimization bet than picking a capture parameter: the argument processing semantics are closer, it's already a bit faster, will allow for more natural code, and has future potential to be every bit as fast as, if not faster, than |c.

like image 61
Jonathan Worthington Avatar answered Oct 02 '22 20:10

Jonathan Worthington