On Mon, 15 Sep 2003, Dan Sugalski wrote: > > Great. But will it also be possible to add methods (or modify them) > > to an existing class at runtime?
> Unless the class has been explicitly closed, yes.
That strikes me as back-to-front.
The easy-to-optimise case should be the easy-to-type case; otherwise a lot of optimisation that should be possible isn't because the programmers are too inexperienced/lazy/confused to put the "closed" tags in.
And it would be a better chance to warn at compile time about doing things which are potentially troublesome.
But whichever way this goes, I take it we'll have warnings like:
Changed method definition Class::foo may not take effect in pending initialiser at program.pl line 9.
Overridden method definition MyClass::foo (new subclass of Class) may not take effect in pending function bar() in zot() at Zot.pm line 5 in other() at Other.pm line 10 at program.pl line 123.
On Mon, 2003-09-15 at 17:39, mar...@kurahaupo.gen.nz wrote: > The easy-to-optimise case should be the easy-to-type case; otherwise a lot > of optimisation that should be possible isn't because the programmers are > too inexperienced/lazy/confused to put the "closed" tags in.
The thinking at the last design meeting was that you'd explicitly say "Consider this class closed; I won't muck with it in this application" at compile time if you need the extra optimization in a particular application.
It's up to the user of a library to ask for that much optimization, not the library designer to consult the entrails whether anyone might ever possibly consider wanting to do something he didn't foresee.
> Because there are some assertions that can lead the optimizer to make some > fundamental assumptions, and if those assumptions get violated or > redefined while you're in the middle of executing a function that makes > use of those assumptions, well...
> Changing a function from pure to impure, adding an overloaded operator, or > changing the core structure of a class can all result in code that needs > regeneration. That's no big deal for code you haven't executed yet, but if > you have:
> a = 1; > b = 12; > foo(); > c = a + b;
> and a and b are both passive classes, that can get transformed to
> a = 1; > b = 12; > foo(); > c = 13;
> but if foo changes the rules of the game (adding an overloaded + to a or > b's class) then the code in that sub could be incorrect.
> You can, of course, stop even potential optimization once the first "I can > change the rules" operation is found, but since even assignment can change > the rules that's where we are right now. We'd like to get better by > optimizing based on what we can see at compile time, but that's a very, > very difficult thing to do.
How about retaining some "debug" info, (line number come to mind), but only at expression level?? So in your example if foo() changed the + operator, it would return into the calling_sub() at expression 4 (numbered from 1 here :-), notice that something has changed, recompile the sub, and continue processing at expression 4.
On Tue, 16 Sep 2003 mar...@kurahaupo.gen.nz wrote:
> On Mon, 15 Sep 2003, Dan Sugalski wrote: > > > Great. But will it also be possible to add methods (or modify them) > > > to an existing class at runtime?
> > Unless the class has been explicitly closed, yes.
> That strikes me as back-to-front.
> The easy-to-optimise case should be the easy-to-type case; otherwise a lot > of optimisation that should be possible isn't because the programmers are > too inexperienced/lazy/confused to put the "closed" tags in.
It would, in fact, be back-to-front.... if performance was the primary goal. And, while it is *a* goal, it is not *the* goal.
From Parrot's standpoint, it doesn't make much difference--once you start spitting out assembly you can be expected to be explicit, so it's a matter of what the language designer wants. While Larry's the ultimate arbiter, and I am *not* Larry, generally he favors flexibility over speed as the default, especially when you can get it at the current speed (that is, perl 5's speed) or faster. You don't lose anything over what you have now with that flexibility enabled, and if you want to restrict yourself for the extra boost, you can explicitly do that.
Then again, he may also decide that things are open until the end of primary compilation, at which point things are closed--you never know... :)
On Mon, Sep 15, 2003 at 03:30:06PM -0600, Luke Palmer wrote: > The focus here, I think, is the following problem class:
> sub twenty_five() { 25 } # Optimized to inline > sub foo() { > print twenty_five; # Inlined > &twenty_five := { 36 }; > print twenty_five; # Uh oh, inlined from before > }
> The problem is we need to somehow un-optimize while we're running. That > is most likely a very very hard thing to do, so another solution is > probably needed.
A naive approach would be to cache the names and positions of things that are optimized such that when one of the cached things are modified, the optimization could be replaced with either another optimization (as in the case above) or an instruction to execute some other code (when we can't optimize the change).
-Scott -- Jonathan Scott Duff d...@lighthouse.tamucc.edu
On Tue, 16 Sep 2003, Ph. Marek wrote: > > You can, of course, stop even potential optimization once the first "I can > > change the rules" operation is found, but since even assignment can change > > the rules that's where we are right now. We'd like to get better by > > optimizing based on what we can see at compile time, but that's a very, > > very difficult thing to do. > How about retaining some "debug" info, (line number come to mind), but only at > expression level??
This is insufficient, since many (potentially most) optimizations result in reordered, refactored, moved, and/or mangled code that doesn't have a line-for-line, or expression-for-expression, correspondence to the original. If it did, this would all be much easier.
The alternative, of course, is to not apply those transforms, but then you're left with pretty much no optimizations.
On Mon, Sep 15, 2003 at 11:49:52AM -0400, Gordon Henriksen wrote: > Austin Hastings wrote:
> > Given that threads are present, and given the continuation based > > nature of the interpreter, I assume that code blocks can be closured. > > So why not allocate JITed methods on the heap and manage them as first > > class closures, so that the stackref will hold them until the stack > > exits?
> Austin,
> That's a fine and dandy way to do some things, like progressive > optimization ala HotSpot. (e.g., "Oh! I've been called 10,000 times. > Maybe you should bother to run a peephole analyzer over me?") But when > an assumption made by the already-executing routine is actually > violated, it causes incorrect behavior. Here's an example:
> my $plugin is My::PluginBase; > $plugin = load_plugin($ARGV[0]); > $plugin.SayHi();
> Now, while it would obviously seem a bad idea to you, it would be > reasonable for perl to initially optimize the method call > $plugin.say_hi() to the function call My::PluginBase::say_hi($plugin). > But when load_plugin loads a subclass of My::PluginBase from the file > specified in $ARGV[0], then that assumption is violated. Now, the > optimization has to be backed out, or the program will never call the > subclass's say_hi. Letting the GC clean up the old version of main when > the notification is received isn't enough--the existing stack frame must > actually be rewritten to use the newly-compiled version.
This discussion seems to contain two separate problems, and I'm not always sure which one is being addressed. The components I see are:
1) Detecting when the assumptions have been violated and the code has to be changed; and,
2) Actually making the change after we know that we need to.
I have at least a vague idea of why #1 would be difficult. As to #2...assuming that the original source is available (or can be recovered), then regenerating the expression does not seem difficult. Or am I missing something?
David Storrs wrote: > This discussion seems to contain two separate problems, and I'm not > always sure which one is being addressed. The components I see are:
> 1) Detecting when the assumptions have been violated and the code has > to be changed; and,
> 2) Actually making the change after we know that we need to.
> I have at least a vague idea of why #1 would be difficult. As to > #2...assuming that the original source is available (or can be > recovered), then regenerating the expression does not seem difficult. > Or am I missing something?
David,
Recompiling isn't hard (assuming that compiling is already implemented). Nor is notification of changes truly very difficult.
What you're missing is what I was trying to demonstrate with my plugin example, and what Dan also pointed out with his mutating a subroutine that returns a constant (and was presumably inlined). If the routine is RUNNING at the time an assumption made by the optimizer becomes invalid, then the stack frame needs to be munged from the old, optimized version compilation to the new, pessimized version. THAT is the hard problem--one of register & variable remapping and PC mutation--and it is impossible to solve after code motion optimizations, for the same reason that C++ debuggers get horribly confused when running over -O3 code.
--
Gordon Henriksen IT Manager ICLUBcentral Inc. gor...@iclub.com
> > This discussion seems to contain two separate problems, and I'm not > > always sure which one is being addressed. The components I see > are:
> > 1) Detecting when the assumptions have been violated and the code > has > > to be changed; and,
> > 2) Actually making the change after we know that we need to.
> > I have at least a vague idea of why #1 would be difficult. As to > > #2...assuming that the original source is available (or can be > > recovered), then regenerating the expression does not seem > difficult. > > Or am I missing something?
> David,
> Recompiling isn't hard (assuming that compiling is already > implemented). > Nor is notification of changes truly very difficult.
Let's try again:
1: sub othersub {...} 2: 3: sub foo { 4: my $a = 13; 5: my $b = 12; 6: 7: othersub; 8: my $c = $a + $b; 9: 10: print $c; 11: } 12: 13: eval "sub othersub { &::infix+ := &::infix-; }"; 14: foo;
; temp_a, temp_b are MY, so suppress them. 7: call othersub ; We do CSE at compile time ; We don't use C after the print, so drop it 9: push 25 9: call print
So when we execute othersub, and notice that the world has shifted beneath our feet, what do we do?
We don't even have the right registers laying about. There are no "a" and "b" values to pass to the operator+ routine. (An advantage, actually: losing an intermediate value would be a worse scenario.)
Two possibilities:
1- Some classes of optimization could be forced to occupy a certain minimum size. The act of breaking the optimization assumptions could replace the optimization (e.g., CSE) with a thunk.
So that we could replace it with: cse: call undo_cse call print
Where presumably undo_cse performed the operations in source code order. (What's more, it would make perl binaries *very* compressible :-)
2- In the "living dangerously" category, go with my original suggestion: GC the compiled code blocks, and just keep executing what you've got until you leave the block. Arguably, sequence points could be used here to partition the blocks into smaller elements.
This tends to make event loops act really stupid, but ...
Good point to raise, but I'm not sure about your conclusion.
12 and 13 don't exist *in registers,* but they do certainly do exist at various points: in the original source, in the AST, and in the unoptimized PASM (if any). The registers were optimized away because the values are statically knowable. So the variables could be resurrected, but the variable-to-register mapping would need to be adjusted. So the optimizer could just emit "temp_a = constant 12" instead of "temp_a = I8" in its metadata for the sequence point.
So my conclusion is not that this optimization is impossible, but that register file consistency isn't enough: High-level variables need to be brought into a findable state. If straight PBC/PASM (not IMCC) were being optimized, it would be the contents of the parrot register file (as written) that must be findable, even though the registers might have been reallocated. By contrast, when IMCC was being optimized (as in your example), then its variables are much more like C variables than they are like registers.
The level at which consistency is achieved at sequence points must be the same level at which the speculative optimization was performed. parrot just provides a particularly large number of intermediate representations, and thus a large number of levels at which said speculative optimizations might be performed: Perl 6 ==parser=> AST ==compiler&optimizer=> IMCC ==compiler&optimizer=> PASM ==assembler=> PBC ==compiler&optimizer=> machine code. So eek. But any optimizer in that chain which doesn't attempt speculative optimizations wouldn't have to worry about them.
> 2- In the "living dangerously" category, go with my original > suggestion: GC the compiled code blocks, and just keep executing what > you've got until you leave the block.
Now, maybe when the invalidated compilation is set loose, parrot could check whether it's on the stack and just flat out emit a warning if so, letting execution of the routine continue even knowing that it might now misbehave. But the programmer will have been informed of it through the warning, so it's kindof okay--so long as the optimizations are applied in a consistent manner. i.e., It is^Wwould be unacceptable that^Wif these crashes^Wwarnings only appear after serving a web page 100,000 times, when HotSpot^Wparrot finally decides to attempt a heavily-optimized compile of your jsp^WMason component.
This strategy makes some amount of sense. Rewriting optimized stack frames is a VERY hard problem--Java 1.4.x provides prior art of that, demonstrating a very long period of instability after HotSpot was introduced. It is VERY difficult to exercise the code thoroughly, has plenty of opportunity to make everything crash, and it's a lot of work (and a lot of code) for something which probably doesn't affect much "good" code anyhow.
How much work is it actually worth to solve this problem, rather than giving the programmer (a) enough information to isolate and diagnose the side-effects and (b) pragmas to turn off the optimizations when needed? The advantages of speculative optimizations and dynamism can both *easily* retained if some (relatively minor?) caveats are accepted.
Let's compare that to Perl 5.
The first example was of an inlined sub returning a constant. This strategy is better than perl 5 would do--perl 5 would just emit a warning and continue to use the old inlined value.
Advantage: parrot
My example was a method-call-becomes-[inlined]-function-call optimization, like that one in HotSpot that Sun's so proud of. For this, perl 5 does better: It would never attempt the optimization, and thus would always behave correctly.
Advantage: perl 5
Your example is overloading the infix "+" operator in mid-stream. Again, Perl 5 doesn't attempt the optimization, so always does the right thing.
Advantage: perl 5
So not entirely clear-cut, although perl 5 does provide (very) limited prior art of the (limited) acceptability of side-effects due to speculative optimizations.
> Arguably, sequence points could be used here to partition the blocks > into smaller elements.
(Breaking code fragments down at sequence points creates a lot more memory fragmentation, reduces locality, adds memory allocation overhead, and complicates branching from a standard PC-relative branch to, what, a PC-relative load + register indirect branch? Ick. I don't think branch history caches would be very happy.)
Sounds like Dan's not keen on sequence points in the first place, since sequence points prohibit code motion optimizations. Assuming that code-motion optimizations take precedence over speculative optimizations, then stack frame re-writing is impossible within that framework, and this entire class of speculative optimizations either (a) must not be implemented, (b) must check before proceeding with the optimized path [and that check may be more expensive than not performing the optimization in the first place], or (c) might allow the program to behave not as written. Any other options?
Option (c) as discussed above might just be okay so long as optimizations are applied predictably and diagnostics are sufficient. It's good enough for perl 5 in limited circumstances. On the other hand, method calls are much more common than inlining constant subroutines: It might not be good enough for parrot, depending upon the speculative optimizations in question.
--
Gordon Henriksen IT Manager ICLUBcentral Inc. gor...@iclub.com
chromatic wrote: > The thinking at the last design meeting was that you'd explicitly say > "Consider this class closed; I won't muck with it in this application" > at compile time if you need the extra optimization in a particular > application.
In Dylan, this is called a sealed class. It tells the compiler that it's safe to resolve method names to slot numbers at parse time, IIRC. Seems like a nice idea.
> chromatic wrote: > > The thinking at the last design meeting was that you'd explicitly > say > > "Consider this class closed; I won't muck with it in this > application" > > at compile time if you need the extra optimization in a particular > > application.
> In Dylan, this is called a sealed class. It tells the compiler that > it's safe to resolve method names to slot numbers at parse time, > IIRC. Seems like a nice idea.
Sounds like a potential keyword, or perhaps a ubiquitous method, or both. But how to differentiate "sealed under optimization" versus "sealed under inheritance"?
Perhaps it would be better to specify an optimizability attribute at some level?
package Foo is optimized "all"; sub foo is optimized("!call") {...}
On Thursday, September 18, 2003, at 07:49 AM, Austin Hastings wrote: > Sounds like a potential keyword, or perhaps a ubiquitous method, or > both. But how to differentiate "sealed under optimization" versus > "sealed under inheritance"?
I don't understand the question.
The point is not for module authors to say "no one can ever extend or modify this class". It's for module users to say "I'm not extending or modifying this class".
> Perhaps it would be better to specify an optimizability attribute at > some level?
--- chromatic <chroma...@wgz.org> wrote: > On Thursday, September 18, 2003, at 07:49 AM, Austin Hastings wrote:
> > Sounds like a potential keyword, or perhaps a ubiquitous method, or > > both. But how to differentiate "sealed under optimization" versus > > "sealed under inheritance"?
> I don't understand the question.
I want CSE and loop unrolling, say, but don't want to prevent polymorphic dispatch by declaring C<my Dog $spot is sealed;> -- if someone gives me a Beagle, I want to call Beagle::bark, not Dog::bark.
> The point is not for module authors to say "no one can ever extend or
> modify this class". It's for module users to say "I'm not extending > or > modifying this class".
> > Perhaps it would be better to specify an optimizability attribute > at > > some level?
On Thursday, September 18, 2003, at 12:33 PM, Gordon Henriksen wrote: > Ah, shouldn't optimization be automatic? Much preferrable to provide > opt-out optimizations instead of opt-in optimizations.
No. That's why I tend to opt-out of writing in C and opt-in to writing Perl.
Perl (all versions) and Parrot are built around the assumption that just about anything can change at run-time. Optimizing the language for the sake of optimization at the expense of programmer convenience doesn't feel very Perlish to me.
On Thu, 18 Sep 2003, Andy Wardley wrote: > chromatic wrote: > > The thinking at the last design meeting was that you'd explicitly say > > "Consider this class closed; I won't muck with it in this application" > > at compile time if you need the extra optimization in a particular > > application.
> In Dylan, this is called a sealed class. It tells the compiler that it's > safe to resolve method names to slot numbers at parse time, IIRC. Seems > like a nice idea.
We'll probably have an optimizer setting for this so you can declare classes sealed at the end of compilation. Parrot'll have to have a means of yelling loudly (and probably throwing a fatal exception) if you try and alter a sealed class at runtime.
This'll likely be a language-dependent setting, as some languages will seal classes by default, which makes some amount of sense in some circumstances.
chromatic wrote: > The point is not for module authors to say "no one can ever extend or > modify this class". It's for module users to say "I'm not > extending or modifying this class".
Ah, shouldn't optimization be automatic? Much preferrable to provide opt-out optimizations instead of opt-in optimizations. C++ const qualifiers, anybody? final in Java? Compressable line noise in most programs, those.... Even--especially--programmers who are writing code in main:: can be naïve of the code they call; they're no more trustworthy than the module author. It's the local user of a class which knows what's going on, and not even then in the case of polymorphic classes. The optimizations enabled by final/sealed are very broadly applicable to most code; would be good to turn them on by default and automatically turn them off when they become inapplicable. DWIM + performance + flexibility. Thus the notifications discussion.
If method call->function call optimization were dicking with a routine and making it misbehave (e.g., forcing excessive recompilation), though, I would want to see a plain old pragma to broadly turn off the optimization. Just:
no optimized <method_calls>;
That way, I could move the pragma down as far as an unnamed block, if I wanted to isolate its effects to one method call, or as far out as the entire module if I was lazy and wanted to do that instead. But no I-promise-not-to-override-methods-of-this-class-anywhere-in-the-entire-p rogram pragma for me, thanks. Way too much action at a distance.
Potentially disruptive optimizations, off by default, could be intuitively enabled by the same pragma, too:
# Try hard to vectorize hyper arithmetic for SSE and AltiVec. use optimized <vector_hyper_ops>;
And it sets up a namespace for optimizations, which might help make optimizations extensible, transparent, or even pluggable.
--
Gordon Henriksen IT Manager ICLUBcentral Inc. gor...@iclub.com
On Thu, Sep 18, 2003 at 02:12:31PM -0700, chromatic wrote: > On Thursday, September 18, 2003, at 12:33 PM, Gordon Henriksen wrote:
> >Ah, shouldn't optimization be automatic? Much preferrable to provide > >opt-out optimizations instead of opt-in optimizations.
> No. That's why I tend to opt-out of writing in C and opt-in to writing > Perl.
> Perl (all versions) and Parrot are built around the assumption that > just about anything can change at run-time. Optimizing the language for > the sake of optimization at the expense of programmer convenience > doesn't feel very Perlish to me.
With Perl6, few people will compile whole librairies but most will load bytecode. At this late stage there is little place for tunable optimization except JITting or it would defeat the sharing of such code between different intances of Perl6. Nothing will preclude to dynamically extend classes. I note that in Perl6 many optimizations were autoloading for deferring compilation of material until it's really needed. With bytecode, it makes sense (at least optimization-wise) that the programmer decides if his classes will be sealed or some methods to be final because at the user level it is too late to decide.
> With Perl6, few people will compile whole librairies but most > will load bytecode. At this late stage there is little place for > tunable optimization except JITting or it would defeat the > sharing of such code between different intances of Perl6. Nothing > will preclude to dynamically extend classes. I note that in Perl6 > many optimizations were autoloading for deferring compilation of > material until it's really needed. With bytecode, it makes sense > (at least optimization-wise) that the programmer decides if his > classes will be sealed or some methods to be final because at the > user level it is too late to decide.
This speaks rather directly to AOP, and to some code metering applications. Being able to do this kind of stuff requires having a nonoptimized version of the code available. I wonder if it makes sense to provide potentially many variants of the compiled code: source, nonopt bytes, opt bytes, and WIA platform? (Alternatively, this might be a hellish nightmare, but I suspect that it would be nice to have "run fast" versions of XML::whatever, and then "single-step" versions.)
Dan Sugalski <d...@sidhe.org> writes: >> Speaking of objects... are we going to have a built-in object >> forest, like Inform has, where irrespective of class any given >> object can have up to one parent at any given time,
> Multiple parent classes, yes.
Not remotely the same thing.
> Parent objects, no.
Oh, well.
>> and be able to declare objects as starting out their lives with a >> given parent object, move them at runtime from one parent to >> another (taking any of their own children that they might have >> along with them), fetch a list of the children or siblings of an >> object, and so forth?
> Erm.... I don't think so. I get the feeling that Inform had a > different view of OO than we do.
I was asking mainly because Perl6 was moving in that general direction. Having compile-time traits but also being able to tag properties on at runtime is very Inform-like. Inform's object model also fits pretty well with Perl's notions of context, things like being able to treat someobject.someproperty as a value and not care whether it's actually a value (the more common case) or whether it's really a routine that returns a value each time (the more flexible case), for example; in Perl6 this will be accomplished more likely by returning an object that has the desired numerify routine so that the caller can just assume it's a number and not get surprised, but that amounts to ultimately the same flexibility.
So I was just wondering about the _other_ useful feature of Inform's object model, the object forest. However, this is the sort of thing that could be added to a later version without breaking any existing code.
> >> Speaking of objects... are we going to have a built-in object > >> forest, like Inform has, where irrespective of class any given > >> object can have up to one parent at any given time,
> > Multiple parent classes, yes.
> Not remotely the same thing.
> > Parent objects, no.
> Oh, well.
Of course, how hard can it be to implement the .parent property?
You'll want it on just about everything, though, so the change will probably be to CORE::MetaClass. It still shouldn't be that hard to do. Maybe Luke Palmer will post a solution... :-)
Austin Hastings writes: > Of course, how hard can it be to implement the .parent property?
> You'll want it on just about everything, though, so the change will > probably be to CORE::MetaClass. It still shouldn't be that hard to do. > Maybe Luke Palmer will post a solution... :-)
Austin Hastings <austin_hasti...@yahoo.com> writes: > Of course, how hard can it be to implement the .parent property?
.parent and also .children, plus .moveto and .remove (which doesn't actually destroy the object but sets its parent to undef, basically, cleaning up the .children property of its parent), and a couple of extra routines for testing ancestor relationships and stuff, but...
> You'll want it on just about everything, though,
Right, it would be less useful if not all objects had it. Although it would be easy enough to implement a class of container object that implemented the forest and each contained a .node which could hold some other kind of object. That adds a layer of indirection, but it would allow for organizing arbitrary objects using the forest.
In Inform any object with no parent is on the forest floor, but it would be easier to implement I think by making the forest floor an object, and having all of the forest-container objects be located there by default and having .remove move them there. Then the forest floor's .children would take care of the ability to iterate over all the toplevel objects.
This approach has the advantage of not needing any changes in core.
Maybe I just won't sweat it. A lot of the things Inform programmers do with the object forest can be done in Perl in other ways, combining hashes and references and arrays and stuff (stuff Inform doesn't really have per se; well it has (statically-sized) arrays, and it sort of fakes references... but it's not the same). Properties are the thing that was harder to get around a lack of, and we're getting those :-)
> > Of course, how hard can it be to implement the .parent property?
> .parent and also .children, plus .moveto and .remove (which doesn't > actually destroy the object but sets its parent to undef, basically, > cleaning up the .children property of its parent), and a couple of > extra routines for testing ancestor relationships and stuff, but...
Sure, no big deal. Also, don't forget the trival matter of moving from a class-based object system to a prototype based one. (Since right now objects don't *have* parent objects (just parent classes), or child anythings) While making things still look like they're a class-based system for all the code and programmers who're used to that.
No problems there, I'm sure. Patches, of course, are welcome.
Dan
--------------------------------------"it's like this"------------------- Dan Sugalski even samurai d...@sidhe.org have teddy bears and even teddy bears get drunk