Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

To warn or not to warn, that is the question

by Ovid (Cardinal)
on Dec 03, 2000 at 11:03 UTC ( [id://44639]=perlquestion: print w/replies, xml ) Need Help??

Ovid has asked for the wisdom of the Perl Monks concerning the following question:

I'm almost thinking this should be in Meditations instead of Seekers of Perl Wisdom, but since I do have a couple of questions, Seekers it is.

I've been thinking recently about using warnings in Perl. Turning on warnings during development is a great thing and should be encouraged. However, sometimes warnings warn you of things that are not really appropriate and you are forced to add code to check for this:

my $x = some_sub(); if ( $x > 2 ) { # do something here }
In the code above, what if some_sub() returns undef? If you have warnings turned on, Perl will complain that you've tried to use an uninitialized value.

So what? I know that an uninitilized value evaluates as zero, so the above code should run fine (assuming that an undef value is not indicative of an issue that I should be addressing in my code). If I want to suppress the warning, I can (in 5.6) use lexically scoped warnings. This is overkill. Prior to 5.6, I could use dynamically scoped suppression of warnings with local $^W. Again, this seems like overkill if I am just turning off warnings for a conditional. The simplest, and most common, way of disabling this warning is the following:

if ( defined $x && $x > 2 ) {}
Well, that's just great. Now I've added some overhead to my program to test for something that's really only a problem because I've used warnings. Ugh! Why should I care if $x is defined if it's just going to evaluate as zero? If the conditional were $x < 2, then I might be worried about whether I want to distinguish an undefined value from zero, but that's not the case here.

Going to such lengths to worry about optimizations seems a bit ridiculous, but to quote gnat in http://prometheus.frii.com/~gnat/yapc/1999-lies/slide3.html:

Speed does matter, just ask your girlfriend. The difference between 2 seconds and 3 seconds is, duh, 1 second. Do that 100,000 times, though, and you've got over a day being wasted. Not every speed problem is the fault of a poor algorithm.
Since I wanted to address this issue, I decided to try a benchmark:
#!/usr/bin/perl use Benchmark; use strict; my ( @nums, $x ); for (1..1000) { my $r = int(rand(5)); push @nums, $r > 0 ? $r : undef; } timethese(-10, { Test_defined => 'for (@nums) { if ( defined $_ and $_ > 2 ) { $x++ + }}', Do_not_test => 'for (@nums) { if ( $_ > 2 ) { $x++ }}' });
This produced the following output on my box:
Benchmark: running Do_not_test, Test_defined, each for at least 10 CPU + seconds... Do_not_test: 11 wallclock secs (10.01 usr + 0.00 sys = 10.01 CPU) @ 3 +65830.27/s (n=3661961) Test_defined: 8 wallclock secs (10.02 usr + 0.00 sys = 10.02 CPU) @ +363485.63/s (n=3642126)
What? The extra test for $_ does not appear to have affected the execution speed. I have run this several times (and for up to 30 CPU seconds for each) and I am getting this result consistently. Am I missing something here? Shouldn't testing whether a variable is defined have some impact on execution speed, no matter how minimal or have I just created a poor benchmark?

I would also like to know the opinion of other monks regarding their opinions regarding irrelevant tests for "definedness". I know that usually warnings for use of undefined variables is a good thing, but I don't want to test for something I don't need to. Additionally, such erros can fill up my error logs and make finding important errors more difficult (though I confess that often those "undefined" errors are highly relevant).

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: To warn or not to warn, that is the question
by extremely (Priest) on Dec 03, 2000 at 14:11 UTC
    My second programming teacher (Basic on CommodeDoor if you insist I age myself) had a wonderful way of asking me questions in the grand greek tradition to make me answer my own questions. He rocked so in Mr McGuire's manner I ask:
    • You mean you aren't checking the return of a sub before using what it returns?
    • Why would you suppress the error system rather than the error?
    • Could you change the sub so that it didn't return a non-numeric answer?
    • Could you do as Mr. Fastolfe says and ensure the value of $x with the lovely my $x= subr() || 0;?
    • Do you want programming in perl to be safe or sloppy?
    • Testing for "definedness" isn't irrelevant in and of itself, do you really think that your testing of it was? Did "-w" think it was?

    Ok I have to be declarative, I can't stand it. =) The flaw in your example is that you aren't controlling the issue in the correct place. If you wish to use some_sub() as numeric, make sure it returns a numeric value. Warnings are a contract with yourself, keep the contract at both ends. If you are explicitly ignoring a return (i.e. if the sub could return both 0 and undef) then don't get hung up on making that choice explict.

    Also, while you are benchmarking, try the same test with warnings on and off and try turning the warnings on and off within the loop you test as well.

    I'm willing to go out on a limb and bet the bit-flag test that defined() does is way cheaper than turning on and off warnings and _majorly_ cheaper than printing errors.

    Perl is still doing the test for defined, it has to for greater than so it knows to promote $_ to 0 for the comparison. All you did is ask for the result of that test.

    --
    $you = new YOU;
    honk() if $you->love(perl)

(jcwren) Re: To warn or not to warn, that is the question
by jcwren (Prior) on Dec 03, 2000 at 11:35 UTC

    On the zero/undef/non-zero'edness of something, this is low overhead.

    #!/usr/local/bin/perl -w use strict; { if (my $x = (maybe_undef_maybe_not () || 0)) { print "Not Zero\n"; } else { print "Zero\n"; } } sub maybe_undef_maybe_not { return undef; }
    --Chris

    e-mail jcwren
Re: To warn or not to warn, that is the question
by Fastolfe (Vicar) on Dec 03, 2000 at 13:08 UTC
    What about something like this:
    my $x = some_func() || 0;
    This will ensure $x has a numeric 0 in it even if some_func gives us an undefined value. I wonder if this impacts performance as much as a 'defined' test...?
Re: To warn or not to warn, that is the question
by btrott (Parson) on Dec 03, 2000 at 22:08 UTC
    Your benchmark is meaningless because @nums is not in scope when the benchmarked code is executed. That's because it's a my variable and has lexical scope; when the code is executed in a different scope (package Benchmark), @nums is no longer in scope. So you're doing your tests on an empty array.

    Instead of a my var you should use use vars.

    When I run your test having done that I get this:

    Benchmark: running Do_not_test, Test_defined, each for at least 10 CPU + seconds... Do_not_test: 10 wallclock secs (10.18 usr + 0.00 sys = 10.18 CPU) @ 7 +98.13/s (n=8125) Test_defined: 10 wallclock secs (10.16 usr + 0.00 sys = 10.16 CPU) @ +671.75/s (n=6825)
    So there is a difference.
Re: To warn or not to warn, that is the question
by eg (Friar) on Dec 03, 2000 at 13:21 UTC

    Actually it does look like the extra test for $_ affects execution speed.

    Do_not_test: 11 wallclock secs (10.01 usr +  0.00 sys = 10.01 CPU) @ 365830.27/s (n=3661961)
    Test_defined:  8 wallclock secs (10.02 usr +  0.00 sys = 10.02 CPU) @ 363485.63/s (n=3642126)
    

    Ignoring the wallclock seconds, Do_not_test ran about 2,000 more times per second than did Test_defined (or about 0.5%). Okay, so it's not going to bring the CPU to a grinding halt, but every cycle counts (especially if, like me, you're still working on an old, old computer :)

    Oh, and I think my $x = sub() || 0; is a good idea.

Re: To warn or not to warn, that is the question
by repson (Chaplain) on Dec 03, 2000 at 14:32 UTC
    It seems to have an effect to me...I also tried using a few other constructs...
    timethese(-70, { and => 'my $m=0; for(@nums){if ($_ and $_>2) {$m++}}', or => 'my $f=0; for(@nums){if (($_||0)>2) {$f++}}', def => 'my $n=0; for(@nums){if (defined $_ and $_>2) {$n++}}', base => 'my $q=0; for(@nums){if ($_>2) {$q++}}' }); This produced: Benchmark: running and, base, def, or, each for at least 45 CPU second +s... and: 49 wallclock secs (47.71 usr + 0.05 sys = 47.76 CPU) @ 60 +77.60/s (n=290266) base: 44 wallclock secs (44.82 usr + 0.18 sys = 45.00 CPU) @ 62 +05.20/s (n=279234) def: 47 wallclock secs (44.96 usr + 0.04 sys = 45.00 CPU) @ 60 +34.82/s (n=271567) or: 45 wallclock secs (45.00 usr + 0.02 sys = 45.02 CPU) @ 60 +34.41/s (n=271669)
    This produced the results I generally expected...barreling along without tests allows us to go slightly faster. If you test more things you get even slower. However suppressing undefined warnings is often not good because undef is sometimes a 'this call didn't work' value or it is an indication of lack of defaults. For example in parameters to a cgi script it is better if you need to explicitly specify what you want where nothing is specifed (forcing you to think about what you do want).
Re: To warn or not to warn, that is the question
by chromatic (Archbishop) on Dec 04, 2000 at 00:35 UTC
    The problem with programming is that something may be irrelevant now, but will become A Learning Opportunity in three months to a year.

    I'm all for optimization, when appropriate, but my corrolary to gnat's law is, "A program that executes completely -- but could be made to run slightly faster with slightly dodgier programming -- is faster than a program that falls over when it hits something you didn't plan for."

    Depending on your design (specifically the abstraction and encapsulation), it may be appropriate to declare that your subroutine always returns numeric values. It may not. If there's a chance that the return value is undefined, I'd definitely do the check.

    If that's the last optimization point in my program, I've done really, really well.

Re: To warn or not to warn, that is the question
by clemburg (Curate) on Dec 03, 2000 at 18:48 UTC

    I have to agree with extremely here. You are testing downstream of where the problem really is.

    When deciding questions like this, I like to focus on "What is the set of values that my function (expression, test, ... ) should accept?". After determining this, I write the function to handle those cases. I *want* to get warnings when values outside of my expected input set are given to my function. I even might code some calls to die() just to make sure I will notice when this happens.

    In the example above, I would thus recommend to make sure some_sub() returns a value that makes sense to the rest of the program (e.g., only numeric values).

    This line of reasoning may seem problematic if you think of generic routines, e.g., dispatching code that uses dynamic handler tables. It is not. If you assume anything about the functions you want to dispatch to, or get return values from, you better make that assumption public, either by encapsulating your assumptions in an interface (e.g., an abstract data type with operations to handle input arguments and return values), or by documenting your assumptions (not the real thing, but better than nothing).

    Turning off "-w" in production code "so I don't see that pesky warnings stuff" (the other "solution" to your problem) makes me extremely nervous. Generally, if people argue like that, there will be problems. Tip: turning "-w" on in big applications that have it turned off often makes for interesting reading and hours of insight and fun.

    Christian Lemburg
    Brainbench MVP for Perl
    http://www.brainbench.com

(tye)Re: To warn or not to warn, that is the question
by tye (Sage) on Dec 04, 2000 at 12:44 UTC

    I'll echo several people and say that undef is not 0 and if there is no difference between the function returning 0 and it returning undef, then the function needs to be fixed to stop returning useless undefs. If there is a difference, then you need to not only tell Perl but tell whoever will next maintain your code that you consciously want to treat undef as if it were 0 because in this specific case that makes sense.

    And trying to optimize 0.5% out of one tiny part of your code (so that your total CPU savings are more like 0.005%) at the cost of less clear code is madness.

    But, I do feel your pain when it comes to dealing with undefined values without generating warnings. This is such a common problem that over and over again someone proposes an alternate form of ||, call it ||| this time, that works on undefinedness instead of falseness. That way you can write:

    my $x= some_sub() ||| 0;
    or
    my $x= some_sub(); $x |||= 0;
    Now in this case, || and ||= actually work just fine. But if you wanted to treat undef as -1, for example, then || would also treat 0 as -1 (probably not what you want). This has been spinning around in my head for some time and def -- Deal nicely with undefined values is what has finally popped out. You'd use it like:
    my $x= def( some_sub(), -1 );
    or
    my $x= some_sub(); def( $x, -1 );
    With no second argument, it defaults to "", which is what I find I want the most often. In a void context it actually modifies its first argument. If you pass in a true value as a third argument, then def() modifies its first argument even if def() isn't used in a void context.

            - tye (but my friends call me "Tye")
Re: To warn or not to warn, that is the question
by FouRPlaY (Monk) on Dec 04, 2000 at 23:48 UTC
    This reminds me of the "Bart's Comet" episode of the Simpsons. Right at the end, after the comet burns up in the polluted air, Moe yells: "Now let's burn down the observitory so this'll never happen again!"

    To use another analogy (since all the good real answers have been used): Sure, you could drive around town at 200 mph. You'd get to where ever your going REALLY quickly. But you may crash. So you'd probably be better off driving so a little slower so you can be safer.



    FouRPlaY
    Learning Perl or Going To die() Trying

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://44639]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (6)
As of 2024-04-18 03:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found