Skip to content
This repository has been archived by the owner on Nov 16, 2020. It is now read-only.

Should we make $_ no longer be is dynamic in 6.d (plus provide mitigation for where it needs to be)? #10

Closed
jnthn opened this issue Aug 19, 2018 · 11 comments

Comments

@jnthn
Copy link
Contributor

jnthn commented Aug 19, 2018

Background

All of $/, $!, and $_ default to is dynamic. This means that they can be accessed through CALLER:: and similar introspection features. For $/, this is used heavily to set $/ from various bits of the regex matching mechanism. For $!, I'm not aware of any current usages. For $_, the only usage I'm aware of so far is in the various P5 compatibility modules, which use CALLER::<$_> in order to emulate the "works on $_ by default" semantics of the Perl 5 built-ins.

Performance considerations

Consider for ^10_000_000 { }. This works by invoking the block with the current value, thus populating its $_. In this case, the loop is optimized such that the iteration counter is done with native values, the boxing taking place in the parameter handling of the block.

Ideally, we'd be able to see that the boxing is not required and eliminate it. Since boxing is a memory allocation of an object that then needs to be GC'd, that is potentially significant. Of course, we don't really care about it for the purpose of an empty loop body, which no real program would ever contain. But what if the loop does contain something? Can we be sure that it won't ever do CALLERS::<$_>?

The immediate answer is, "as soon as we have late binding, then no". That means "as soon as we have a method call", for example. One might wonder if we can't just let inlining take care of matters: if the loop is hot enough, we will hopefully inline all of the things anyway, and then we can see what the code does. While that can indeed happen, it's not so simple. We do a lot of speculative optimization, with guard clauses triggering deoptimization. Just because our optimized code can never take a path where CALLERS::<$_> is used doesn't mean that it won't deopt and then find itself on a place where it is used.

Another good question is, "but can't the upcoming work on partial escape analysis save us", and the answer is "yes, but..." We could:

  • Use normal local registers to model the lexical store
  • Flush them to the real lexical store at the point we either make a non-inlined call or perform deopt, since that's where they may escape

This would work great for our microbenchmark here, and others where we manage to fully inline everything, or at least where the non-inlined path is taken rarely, so the $_ only sometimes escapes. But for for ^10_000_000 { $obj.some-very-big-method } or similar, we'd not be able to do much apart from assume the worst.

I've picked the for case as a concrete example, but $_ usage is incredibly widespread in idiomatic code:

  • given $foo { }
  • with $bar { }
  • .map({ .foo + .bar })

And in all those we'll run into the same optimization challenges.

For $/ and $!, I'm not that concerned: the former would be swallowed up by the cost of regex handling anyway, and the latter only shows up in exceptional cases, which by definition are rarely taken.

How much does this matter?

I believe the bar should be set pretty high for "we should change the language because X is hard to optimize". There's many things in Perl 6 that are challenging to make run fast, but we're making good progress on that anyway. To put this in to perspective, the current situation is that given these:

# Perl 6
for 1..10_000_000 {
}
# Perl 5
for (1..10_000_000) {
}
# Python
for x in range(0, 10000000):
    pass
# Ruby
for i in 1..10_000_000
end

Then currently, measured on my none-too-fast home VM and using the experimental postrelease-opts version of MoarVM, we get:

  • Perl 6: 0.484s
  • Perl 5: 0.247s (so 1.95x faster than Rakudo)
  • Python: 0.565s (so Rakudo is 1.17x faster Python)
  • Ruby: 0.419s (so Ruby is 1.15x faster than Rakudo)

If we were to normalize for startup time, we'd be beating Ruby and within 1.3x of Perl 5 (but I don't really think we should be hiding our startup time, but rather improving it :)). But then consider:

my int $i = 1; while $i++ <= 10_000_000 { }

Which runs in 0.261s. We should be able to optimize the range case in to this, and granted, with sufficiently smart PEA we'll be able to. But as noted, microbenchmarks are not representative of real programs, where "inline everything" isn't really realistic.

Usage of $_ as a dynamic

I'm only currently aware of use of $_ being dynamic by the Perl 5 compatibility modules. I'm partly writing this to solicit input from those who may be aware of other cases that I have failed to consider. If that really is the only use of this feature, we might want to consider picking a more optimizable and analyzable default scope for $_ (e.g. "normal lexical").

Mitigation for modules relying on $_ as dynamic

I'd propose in 6.d

  • A pragma, use dynamic <$_> or similar, for indicating that we want $_ to get dynamic scope
  • A means to export pragmas to the user of the module (perhaps an EXPORTPRAGMA to go with EXPORTHOW or some such), so that the P5 compat modules can do that and require their users to do nothing different than they do today

Further benefits

  • The programmer can consider $_ the same way they consider their other lexicals: if you can't textually see it being changed and you aren't passing it somewhere, it won't be being changed
  • Other analysis tools can do that same (in the absence of the pragma)
  • We'd get a general pragma export mechanism out of it

Drawbacks

  • It breaks the consistency that "all the magic variables are is dynamic"
  • The new pragma and export mechanism would need implementing, tests, docs, etc. (I'd be willing to do that, but it's more that it's an ongoing cost, like everything we add)
  • It feels like a small defeat to propose a language change rather than just getting cleverer at building an optimizer :-)
@softmoth
Copy link

I'm in favor of the change.

The "all magic vars are is dynamic" rule isn't one I ever rely on, and doesn't seem to be that useful in my mind. I'm not sure I've ever considered it.

For any future Perl 6 compilers this will be an issue. It feels appropriate to design the language to be efficient if we're not really losing any practical benefit.

Having $_ be dynamic by default is, in my experience, sometimes really handy and sometimes an annoying feature I have to work around. Lexical by default would simplify its usage by new Perl programmers.

@niner
Copy link

niner commented Aug 20, 2018 via email

@lichtkind
Copy link

going for speed now feels right to me

@Juerd
Copy link

Juerd commented Sep 3, 2018

Performance is incredibly important for Perl 6.

I think a fast runtime is a much bigger selling point for Perl 5 programmers, than 5to6 migration modules could ever be. They probably wouldn't mind explicitly passing $_ to Perl 5 compatibility functions, or using Perl 6 methods instead, if that meant Perl 6 could be significantly faster, so I think the proposed mitigation pragma is not very important.

@nxadm
Copy link

nxadm commented Sep 4, 2018

Rakudo Perl 6 has a lot to offer to Perl 5 programmers: concurrency, nice OO, grammars, lots of built-in functions. These things are not in core Perl 5, so it's not like you can transpose Perl 5 constructs most of the time.

Speed, next to the smaller ecosystem, is often of of the causes for some interested Perl 5 programmers not to use Perl 6 yet.

@polettix
Copy link

polettix commented Sep 4, 2018

Just a few questions/thoughts:

  • Would this surprise people using Perl 6, e.g. when coming from Perl 5 (but not necessarily)?

  • Would it make sense to go the other way around, and add a pragma/whatever to turn is dynamic off instead when needed? Then maybe plan to make it the default behaviour in the medium-long run if it comes to be the default usage by most people?

  • Personally, in Perl 5 I try to stay away from $_ as much as possible exactly because the magic might bite me in places I don't anticipate. I have a vague reminiscence of a big failure when $_ has been made lexical in Perl 5 tough, but I can't really say if we risk the same here.

@duncand
Copy link

duncand commented Sep 4, 2018

I agree with Juerd 110%. Its worth people explicitly passing $_ to Perl 5 compatibility functions in order for Perl 6 to gain performance, and its best to just make the change and not introduce a pragma.

@zoffixznet
Copy link
Contributor

Don't know if it affects it, but FWIW there's some propspec that uses CALLERS::<$_>; dunno if it needs to be changed: Raku/roast@8995cd75e

@jnthn
Copy link
Contributor Author

jnthn commented Oct 15, 2018

Thanks for all the comments. A few responses:

Would this surprise people using Perl 6, e.g. when coming from Perl 5 (but not necessarily)?

Unlikely, because none of the Perl 6 built-ins default to using the caller's $_. Of course, that might be a surprise, though as noted, some folks actively avoid said Perl 5 feature, so it might also be a relief. :-)

Would it make sense to go the other way around, and add a pragma/whatever to turn is dynamic off instead when needed? Then maybe plan to make it the default behaviour in the medium-long run if it comes to be the default usage by most people?

For a widely used feature, I think such a soft migration path would be more sensible. For one that seems barely used, it doesn't seem likely to help anyone.

Its worth people explicitly passing $_ to Perl 5 compatibility functions in order for Perl 6 to gain performance, and its best to just make the change and not introduce a pragma.

Well, yes, there is a point here that if the Perl 5 compatibility modules export the pragma, then they enforce missed optimizations on code that uses them. I figure those are a porting aid, though, so behavioral similarity trumps speed.

Don't know if it affects it, but FWIW there's some propspec that uses CALLERS::<$_>; dunno if it needs to be changed

Yes, I'll have to review those. Thanks.

@vrurg
Copy link

vrurg commented Aug 18, 2019

I wonder if this ticket was ever implemented? Current state of things is that CALLER::<$_> fails with Cannot access '$_' through CALLER, because it is not declared as dynamic.

Roast test APPENDICES/A03-older-specs/01-misc.t has a specific subtest for this semantics which is TODOed but must either test for CALLER::MY::, CALLER::LEXICAL::, or CALLERS::, or be removed altogether.

@vrurg
Copy link

vrurg commented Aug 20, 2019

Got a confirmation that $_ is not dynamic since 6.d release. Closing.

@vrurg vrurg closed this as completed Aug 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants