Current subroutine signatures implementation contains two features which purposes are different

I want to write about subroutine signatures more.

This is previous topic.

I think subroutine signatures don't need arguments count checking

Current subroutine signatures contains tow difference features

  1. Syntax suger of my ($x, $y) = @_
  2. Arguments count checking

My opinion is that this two features has different purpose. First is for syntax sugar. Second is for aruments count checking.
I think it is good to separate two features because each feature has different purpose.

I don't hope "perl subroutine + (syntax suger + argument count checking)".

I hope "perl subroutine + syntax sugar + argument count checking".

It is not good that different purpose features is mixed into one feature.

I want syntax sugar and I don't need argument count checking in my program. This is natural for me.
but there are people who want also argument count checking.

We should not assume all people want arumengt count checking.

Syntax sugar is the feature most poeple wait, but argument count checking is not.

It is safe implementaion in the Perl future that any perfomance cost don't force to user

I can't show benchmark because this discussion is not for current implementation.

My point is that "It is safe implementaion in the Perl future that any perfomance cost don't force to user".

If the performance of subroutine call will improved in the future, the performance limitation maybe argument count checking.

And I think it is good that subroutine signatures is designed for the improvement of subroutine performance, not usability.

9 Comments

If you want the highest execution performance, Perl is usually the wrong choice. Many many comparisons have shown languages like C, C++, Java, Go, compiled Lua, Assembler, etc. to be immensely faster than Perl for some given task.

So nobody chooses Perl for raw execution performance.

Reasons why people choose a slower language like Perl include:
- in-house knowledge of the language (compared to others)
- easier / faster to program and debug (programmer efficiency), including
CPAN giving you lots of ready to use building blocks
- easier to maintain
- "making simple things easy and difficult things possible"
, and that while still having acceptable performance.

Imagine you were maintaining central heating and you turned it off in mid-winter: "I have woolen blankets and it will save lots of money", while most other people in the house freeze and get sick. That's not really the selling point of central heating, is it?

Same with Perl: trading what people choose Perl for (like programming and debugging speed and ease) for what they do not choose Perl for (highest execution speed) is simply a dumb move.

Especially when the win in performance likely is miniscule - it'd be somewhat different if you had benchmarks proving that it was a significant performance hit.

It also is a case of premature (way way premature) optimisation:
"The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming." (Donald Knuth, 1974 Turing Award Lecture, Communications of the ACM 17 (12), (December 1974), p 671)
"Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%." (Donald Knuth, "Structured Programming with Goto Statements". Computing Surveys 6:4 (December 1974), pp. 261–301, §1.)

I am fine with giving you the ability to shoot into your own foot. But if you want to do that, it's only fair you activate that ability. And --- if you were actually profiling those of your programs that are too slow, you'd find that there will be a couple hotspots --- and these would be the only places where disabling argument checking might give a (still miniscule) increase in speed.

So putting a ":no-argument-checking" to 4 or 5 functions is not much of a hassle, whereas putting ":strict" at almost every subroutine definition is very onerous. And asking everybody else who does not care for miniscule performance improvements, but a lot for catching errors early, to do that is very very self-centered of you. Perl 6 made the right decision to have "use strict; use warnings;" automatically set. You can still unset it, if you need to.

What you likely really want is to "use Inline C => ...;". Or change your algorithm to a faster one. This will give you way more speed.

[1] 99.999+%
[2] If they did care that much, they'd not choose Perl!

This new research might be relevant: https://arxiv.org/abs/1604.03641

I wanted to see how big of a deal this is - I compared 4 different methods with around 10,000,000 sub calls. The subs did almost nothing (added the two args to a state variable and returned the state variable). I called the sub with an array as the parameters - I.E. test_call(@arr). I tested in 5.22.1 on MacOS.

#1 - sub test_call($a, $b)
#2 - sub test_call($a, $b, @)
#3 - sub test_call($a=undef, $b=undef, @)
#4 - sub test_call { my ($a, $b) = @_

Test #1 - 4.8s
Test #2 - 4.0s
Test #3 - 3.8s
Test #4 - 3.3s

So, if sub call speed is your critical need, use method #4. Of course as other commenters mentioned, I'd probably write that chunk in C or assembly, refactor my algorithm, or unroll my loop.

I also changed the sub for #5 as follows:

sub test_call {
state $foo = 0;
$foo += shift() + shift()
}

This was, by far, the fastest - at 1.9s. Much faster than creating the my variables in #4. So I guess variables shouldn't generally be used...

I tested a different construct, #6, which was basically #4 using global variables instead of lexically scoped variables - that was 4.4s, which is a lot slower than the 3.3s with lexically variables.

If speed of sub calls is critical for you and you aren't going to rewrite it in C or ASM, I'd probably try several different options, benchmarking all of them. But I wouldn't want to maintain that code most likely. In the meantime, just add an "@" at the end of your signature and you'll reduce the cost of your calls if it is important to your code, but still get the benefits of checking that your required args were passed.

I understand the need for speed, but as benchmark above shows, you are only losing a second for every 10M calls - if you are doing anything remotely noon trivial in those subs you will not notice the time paid for correctness.

Besides, it seems that as per benchmark #5, if you needed to hyper optimize code you would need to avoid destructuring anyways, regardless of argument checking.

Lastly, can you point to any consensus that correctness should take back seat to performance in perl?

Thanks

I'm also genuinely interested in your argument that dynamic language implies no argument checking, do you mind elaborating a bit more on it?

Hi Yuki, thanks for your response.

I am always looking to learn better software engineering techniques. I hope you don't mind me pressing for more details from you. Is it only the performance impact of arg count check that bothers you? Or do you find yourself often wanting to define

sub foo($bar) { ... }

and then use it as

foo( $bar, $baz );
# or maybe
foo( @only_first_matters )

I am curious to discover any techniques that revolve around these patterns?


Re: correctness, I can only offer my own experience with a legacy code base. I started to `use Method::Signatures` just for the sugar whenever touching any code on this project - I also didn't care for argument counting, because, who would ever pass incorrect number of arguments was my thinking.

But right away the argc checks started revealing bugs in various code, and it was a great way to "fail early" rather than hunt the bug few levels deep. I now sometimes use it for gradual typing too, since it allows to do `func foo(Bar $baz)` and it will check that `$baz isa Bar`. It has had a positive effect while we worked to modernize, refactor and add tests to this project. YMMV, but I certainly found benefit to arg counting enforcement and our project is very I/O heavy, so we don't see any performance impact.

Thank you for your input

Leave a comment

About Yuki Kimoto

user-pic I'm Perl Programmer. I LOVE Perl. I want to contribute Perl community and Perl users.