8

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?

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
drclaw
  • 2,463
  • 9
  • 23
  • 1
    When using subroutines the capture `|c` will take 'Unexpected named argument(s)' that a non-flattening slurpy `**@c` won't. – ugexe Apr 16 '19 at 17:18
  • 1
    See also [my answer to SO **variable number of arguments to function/subroutine**](https://stackoverflow.com/a/54713872/1077672). – raiph Apr 16 '19 at 18:42
  • I guess the real question is what are you going to do with c? – Curt Tilmes Apr 16 '19 at 19:24
  • @CurtTilmes no direct plans, just to learning about Captures and looking for simple performance gains for calling subs repeatedly a large number of times – drclaw Apr 16 '19 at 21:40
  • I don't know that there is a particular down side -- if it works in your situation, use it! – Curt Tilmes Apr 16 '19 at 22:01

1 Answers1

10

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.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
  • A quick test with ` |c`, `+@c`, `**@c` and `**@c is raw` give durations of `0.8427187, 2.593368, 2.8757735 and 2.8949438`. It probably depends on the use case but the `Capture` seems the quickest in this simple test. – drclaw Apr 17 '19 at 06:34