Search code examples
perlpackagesymbolsmicrobenchmarkconstantfolding

Is the difference between these two evals explained with constant folding?


Given these two evals which only change Module::FOO() and FOO().

# Symbols imported, and used locally.
eval qq[ 
  package Foo$num;
  Module->import();
  my \$result = Module::FOO() * Module::FOO();
] or die $@;

# Symbols imported, not used locally referencing parent symbol.
eval qq[ 
  package Foo$num;
  Module->import();
  my \$result = FOO() * FOO();
] or die $@;

why would the top block take up substantially less space? The script and output are reproduced below,

Script

package Module {
  use v5.30;
  use warnings;
  use constant FOO => 42; 
  use Exporter 'import';
  our @EXPORT = ('FOO');
}


package main {
  use v5.30;
  use autodie;
  use warnings;

  $|=1;
  say "Our PID is $$";

  for my $num ( 0..1e5 ) { 
    eval qq[ 
      package Foo$num;
      Module->import();
      my \$result = Module::FOO() * Module::FOO();
    ] or die $@;
    eval qq[
      package Foo$num;
      Module->import();
      my \$result = FOO() * FOO();
    ] or die $@;
  }

  say "DONE";

  _debug();

}

sub _debug {
  open my $fh, "<", "/proc/$$/status";
  while ( <$fh> ) { 
    next unless /Rss/;
    print;
  }
}

Results

Package (namespace) qualified

RssAnon:      78896 kB
RssFile:       5220 kB
RssShmem:         0 kB

Locally imported

RssAnon:     168180 kB
RssFile:       5496 kB
RssShmem:         0 kB

Solution

  • This is a result of the constant folding inside of Perl. This can be demonstrated with the following example from haarg on irc.freenode.net/#perl,

    use strict;
    use warnings;
    
    package Module {
      use constant FOO => 42;
      use Exporter 'import';
      our @EXPORT = ('FOO');
    }
    
    my $subs = {
      loc => sub {
        package Foo;
        Module->import();
        my $result = FOO() * FOO();
      },
      fq => sub {
        package Foo;
        Module->import();
        my $result = Module::FOO() * Module::FOO();
      },
    };
    
    use Data::Dumper;
    $Data::Dumper::Deparse = $Data::Dumper::Indent = 1;
    print Dumper($subs);
    

    That will result in,

    $VAR1 = {
      'loc' => sub {
          package Foo;
          use warnings;
          use strict;
          'Module'->import;
          my $result = FOO() * FOO();
      },
      'fq' => sub {
          package Foo;
          use warnings;
          use strict;
          'Module'->import;
          my $result = 1764;
      }
    };
    

    You can see one of them has,

    my $result = FOO() * FOO();
    

    While the other has,

    my $result = 1764;
    

    You can get constant folding in both if the module is declared and the import is done in the compiler phase (in the case of a sub compilation), or before the execution in the case of a stringy-eval, like this

    BEGIN {
    
        package Module {
          use constant FOO => 42;
          use Exporter 'import';
          our @EXPORT = ('FOO');
        }
        package Foo { Module->import() }
    
    }