5

The reason I want to use anonymous subs instead of named ones is because I want to define these subs inside Mason subcomponents(http://www.masonbook.com/book/chapter-2.mhtml#TOC-ANCHOR-7), which don't behave well with named subs.

E.g. if I write code in that way:

my ($first, $second);
$first = sub {
    my $val = shift;
    print "val: $val";
    $second->($val);
};
$second = sub {
    my $val = shift;
    if (0 < $val) {
        $val = $val - 1;
        $first->($val);
    }
};
$first->(10);

Are there any hidden gotchas(e.g. memory leaks, etc.) in this approach?

As explained by @Schwern, memory for these subs won't be released by Perl, as there's a circular reference between them.

But more specifically, will the memory allocation grow linearly, as $val is increased, or it doesn't depend on invocation stack depth? Because I can put these subs in mason <%once> blocks, and in that case these subs will be initialized only once.

Yakov
  • 53
  • 5

2 Answers2

5

The only one I can think of is that the subroutines will never be deallocated, even if $first and $second go out of scope. $first's code refers to $second, $second's code refers to $first. This is a circular data structure, and Perl's memory allocation cannot deallocate that.

$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub {};  $second = sub {} } say "Done"; sleep 1000'

$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() } } say "Done"; sleep 1000'

The first Perl process uses 1912K after the loop, the second uses 10320K. The first will not grow no matter how many CVs are created, the second will.

To get around this, you have to break the circle by undefining $first or $second. This third one calls undef $first inside the loop, its memory does not grow.

$ perl -wlE 'for (1..100_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() }; undef $first; } say "Done"; sleep 1000'
mpapec
  • 50,217
  • 8
  • 67
  • 127
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • Thanks guys! But basically, if I define anonymous subs outside of the loop, my memory footprint won't raise? – Yakov Sep 29 '15 at 19:39
  • 1
    Loop only exaggerates the problem; you still have to undef $first or $second function to avoid circular structure. – mpapec Sep 29 '15 at 19:46
  • @Yakov The loop is only there to illustrate the leak. It A) provides a lexical context (ie. block) where the subroutines would otherwise be destroyed upon exiting and B) exaggerates the memory leak so it will show up in a coarse tool like `ps`. – Schwern Sep 29 '15 at 19:54
  • @Yakov, If the code isn't in a loop of some sort, then it's hard to picture why anon subs would be needed at all. Along the same line, if there is a need for anon subs, then Schwern's solution for freeing them probably can't be used. – ikegami Sep 29 '15 at 20:19
2

The following would be fine:

sub first {
    my $val = shift;
    print "val: $val";
    second($val);
}

sub second {
    my $val = shift;
    if (0 < $val) {
        $val = $val - 1;
        first($val);
    }
}

first(10);

The only caveat is that you'll need to declare the subs if they have a prototype or if you wish to omit the parens around their arguments.

sub first($);
sub second($);

sub first($) {
    my $val = shift;
    print "val: $val";
    second $val;
}

sub second($) {
    my $val = shift;
    if (0 < $val) {
        $val = $val - 1;
        first $val;
    }
}

first 10;

Your version, on the other hand, has a memory leak. The first sub captures a reference to the second sub, which captures a reference to the first sub.

$ perl -e'
   sub DESTROY { print "Destroyed\n" }

   {
      my ($first, $second);
      $first = sub { $second };
      $second = sub { $first };
      bless($first);
   }

   print("Subs should have been destroyed by now\n");
'
Subs should have been destroyed by now
Destroyed

The solution depends on why you decided to use anon subs in the first place.

ikegami
  • 367,544
  • 15
  • 269
  • 518