http://www.perlmonks.org?node_id=323984


in reply to Debug code out of production systems

A faster way to compile code out of a system is to hide it behind an if(CONSTANT) or unless(CONSTANT), as in

use constant DEBUG => 0; if (DEBUG) { warn( "This code is only compiled into the program ", " when DEBUG is true.\n"; ); }
The bonus is that you don't introduce more delay in the compile time, which a lot of people apparently dislike. I discovered this in POE, a project where I gained about 20% runtime performance with POE::Preprocessor by replacing small, commonly used functions with macros. A contrived example:
macro num_max (x,y) { ((x) > (y) ? (x) : (y)) }

This macro is then used, template-like, in the main body of source as:

print "You owe: \$", {% num_max $total-$paid, 0 %}, "\n";

Back to compile-time inclusion. POE::Preprocessor uses the common if/elsif/else syntax, tagged with an "# include" marker. That is, if you comment a construct with "# include", it will be evaluated at compile time (using the CONSTANT trick), and the code in the block will be included (or not) depending on the condition's outcome.

unless ($expression) { # include ... lines of code ... } elsif ($expression) { # include ... lines of code ... } else { # include ... lines of code ... } # include
Problems with macros and source filters in general:
  1. They alter your source's line numbers, which interferes with warnings and error messages. POE::Preprocessor takes great pains to insert "# line" directives that not only preserve your original line numbers but also indicate where in your macros the problem may really lie.
  2. They confound packagers, most notably perlapp and perl2exe. These Perl "compilers" do not evaluate source filters at runtime. They don't even evaluate them at "compile" time. Instead, the original, non-Perl syntax becomes an error when you try to run things.
  3. Source filtering is slow. I got no end of complaints about slow startup times, even though POE::Preprocessor attempts to be optimal Perl.
  4. Any non-Perl syntax, no matter how trivially like any number of template toolkits, is greeted with shock and confusion. (Heck, people still don't like @_[CONST1, CONST2], even though it is standard Perl syntax.)

Liz's solution is much smarter than mine. It addresses all these problems. Very nice!

-- Rocco Caputo - rcaputo@pobox.com - poe.perl.org

Replies are listed 'Best First'.
Re: Re: Debug code out of production systems
by liz (Monsignor) on Jan 25, 2004 at 19:38 UTC
    ...It addresses all these problems. Very nice!

    Thank you for your kind words. But I'm afraid there are still some issues involved ;-(

    They alter your source's line numbers...

    I've chosen a very simple, line by line algorithm, that will lend itself for rewriting in C if ever necessary. No lines should be removed or added, so line numbers should always be correct (although pod lines that are not activated, are replaced by empty lines). I'm contemplating emptying out lines that start with "#" also, but I'm afraid the additional check (in Perl) would cost more CPU than adding the whole line to the source again and having the Perl parser get rid of such a line (in C).

    ... These Perl "compilers" do not evaluate source filters at runtime...

    Well, add mod_perl to that list. My nice little magic module doesn't do it in mod_perl. ;-( One of the reasons I started this in the first place. ;-(

    Anyway, I have added an API for other modules that would allow them to have an arbitrary piece of code stored in a variable to be processed in the same manner. For instance

    eval $source;
    could become:
    ifdef::process( $source ) if exists &ifdef::process; eval $source;

    Now to find out what magic mod_perl is performing when it loads its Perl modules and convince the mod_perl people to add the above extra line... ;-)

    Liz

    Update:
    Actually, I just realized the above could be done smarter:

    =begin MODPERL ifdef::process( $source ); =cut eval $source;
    Under mod_perl, the environment variable MODPERL is always defined and not null. If ifdef is active, then the extra processing line becomes active automatically, making sure the $source will also be processed. If ifdef is not loaded, then the pod section will be skipped, directly evalling the source.