{
my $fh = IO::File->new;
$fh->open(">test.tmp");
print $fh "a";
}
The filehandle is closed automatically at scope exit and the file
contains the expected contents.
That's quite easy in the current Perl implementation as it does
reference counting. At the closing bracket the last reference to
C<$fh> has gone and the file gets closed during object destruction.
As parrot isn't using reference counting for GC, we have to run a GC
cycle to detect that the IO object is actually dead. This is
accomplished by the "sweep 0" opcode, which does nothing if no object
needs timely destruction.
Above example could roughly translate to Parrot code like:
new_pad -1 # new lexical scope
$P0 = new ParrotIO
store_lex -1, "$fh", $P0
open $P0, "test.tmp", ">"
print $P0, "a"
pop_pad # get rid of $fh lexical
sweep 0 # scope exit sequence
Alternatively a scope exithandler could be pushed onto the control
stack.
With such a sequence we got basically two problems:
1) Correctness of "sweep 0"
At scope exit we've somewhere in the PMC registers the ParrotIO
object still existing. This means that during marking the root set,
the ParrotIO object is found being alive, where it actually isn't.
The usage of registers keeps the object alive until this register
frame isn't referenced anymore, that is after that function was left.
2) Performance
The mark&sweep collector has basically a performance that is
proportional to the amount of all allocated objects (all live objects
are marked and live+dead objects are visited during sweep). So
imagine, the Perl example is preceded by:
my @array;
$array[$_] = $_ for (1..100000);
{
...
The GC cycle at scope exit has now to run through all objects to
eventually find the ParrotIO object being dead (given 1) is solved).
Performance would really suck for all non-trivial programs, i.e. for
all programs with some considerable amount of live data.
During the discussion WRT continuations I've already proposed a scheme
that would solve 1) too.
While 2) is "just" a performance problem, addressing it early can't
harm as a solution might impact overall interpreter layout. But before
going more into that issue, I'd like to hear some other opinions.
leo
If the number of objects that needs this is relatively small, we could
play a trick somewhat like the following (with small changes to the perl
compiler):
1. Break the filehandle object into two: a generic wrapper that uses
refcounting and forwards all calls, plus the actual filehandle object.
2. All invocations on the filehandle object really get the wrapper
object. If the object is passed to another method that stahses a copy,
the reference count will therefore be increased.
3. At the end of the scope, explicitly check the reference count in the
wrapper object. If it is one, call the destructor on the wrapper and
the filehandle. If that is not one, the filehandle has 'escaped', and
will no longer be subject to exact destruction.
The semantics of this are slightly different from the perl5 one, but are
likely to be good enough: there is predictable destruction for objects
destroyed at the end of their declaring scope.
There seems to be a misconception about refcounting, and it's showing up
here. People seem to think that you can do it to certain objects and
not others. The fact is, right now when we're wiggling around PMCs
we're just changing some pointers. But if there is one object that
likes to be refcounted, then *every* pointer change needs to do the
refcount dance. It's a big, potentially cache-blowing speed hit for the
whole virtual machine.
You can't do refcounting selectively. It's all or none.
Of course, in C++, you can, because you tell C++ which pointers are
refcountable pointers at compile time. If, by static analysis, we could
tell Parrot when PMCs need refcounting before it assembled the bytecode,
and also in some other places, we might be able to pull it off. But
that's a halting problem problem if the language from which we're
compiling is untyped.
What I'd most like is to convince Larry to waive the timely destruction
requirement. However, that doesn't really solve the problem for other
languages that need timely destruction. Are there any?
Luke
Out-of-this-world-ly yours,
Michael
Perl 5 springs to mind !!!
--
The crew of the Enterprise encounter an alien life form which is
suprisingly neither humanoid nor made from pure energy.
-- Things That Never Happen in "Star Trek" #22
* First I present some notes on how it could be achieved or approximated
using incremental and generational techniques.
* Second, I note that timely GC only exists in C++.
On Fri, 2005-01-14 at 17:57 -0500, Michael Walter wrote:
> You could change the GC scheme (*cough*) to use one similar to
> Python's (ref-counting + additional GC for cyclic references
> *double-cough*).
You could adapt Java's last-generation GC scheme to do a really fast GC
on scope-exit, only of objects created within that scope. However, this
may require a relocating or treadmill GC to do efficiently. Otherwise, I
seem to remember a zone-based GC strategy which allocated 'zones' to
frames and GC'd by zone... will have to find a reference. It was very
fast, since generally everything from a zone except the return value was
garbage. Vague memories are also coming back to me about only being able
to point from lower to higher numbered zones, except through gates ...
I suspect the algorithm described at
http://www-106.ibm.com/developerworks/java/library/j-jtp11253/
with card-marking on arenas would be applicable. PMCs could be relocated
between arenas (promotion to an older generation?) could then store a
redirect bit in the arena header of the old arena, and update all
pointers on the next full GC (about 1 in 10 in the 1.4 hotspot JVM,
except when thrashing). I note that in practice [and this is
mis-documented] the hotspot JVM keeps separate permanent store, and
appears to thrash on the object heap when permanent store is full,
although this permanent store ought to be treated as a strictly older
generation.
This is all sparse thoughts upon which I will probably have to expand.
> On Fri, 14 Jan 2005 14:40:43 -0700, Luke Palmer <lu...@luqui.org> wrote:
> > Hildo Biersma writes:
> > You can't do refcounting selectively. It's all or none.
> >
> > Of course, in C++, you can, because you tell C++ which pointers are
> > refcountable pointers at compile time. If, by static analysis, we could
I thought C++ only guaranteed destruction (on return or exception) for
objects which were directly on the stack. The idiom for guaranteeing
lock-release on scope destruction is:
void foo() {
Lock foo();
/* critical section */
}
Not
void foo() {
Lock *foo = new Lock();
/* not guaranteed destruction */
}
which is being proposed as the Perl equivalent.
Furthermore, I seem to recall comments throughout Perl's documentation
stating that you could NOT assume that objects were GC'd on scope exit.
> > tell Parrot when PMCs need refcounting before it assembled the bytecode,
> > and also in some other places, we might be able to pull it off. But
> > that's a halting problem problem if the language from which we're
> > compiling is untyped.
> >
> > What I'd most like is to convince Larry to waive the timely destruction
> > requirement. However, that doesn't really solve the problem for other
> > languages that need timely destruction. Are there any?
As noted above, I think only C++ guarantees it. Try the zone-based
incremental-ish GC. Going to find some references.
S.
> > I thought C++ only guaranteed destruction (on return or exception) for
> > objects which were directly on the stack.
>
> That's true, you have to explicitly delete most memory. I was actually
> referring to the template refcounting classes that I use all the time:
>
> void foo() {
> ref<Image> image = new Image("somefile.jpg");
> image->draw();
>
> // image will be cleaned up automatically
> }
>
> Here, C++ manages only to refcount objects (or rather, pointers) that
> are declared using ref<> instead of *. In a dynamically typed VM this
> isn't possible.
Yeah, but the ref<> object is directly on the stack, and destruction of
that, and thus decrement of the refcount, is guaranteed. Um.
The example you described destroyed a ref within a sub, and then assumed
that the object refed would be destroyed at scope exit.
This is incorrect: the object refed should be destroyed when the ref is
destroyed in a general refcounting GC system.
Anyway, this led me to the (useful?) thought that you could delink the
concept of timely GC and destruction at scope exit by registering
atexit() or atleave() code on a scope (like finally{}) which explicitly
undef'd registered PMCs. That way you would be able to guarantee
destruction even if the PMC was still ref'd, which is probably more use
for things like critical section locks than just relying on a
side-effect of the GC, and is certainly easier to implement since it's
just a flag on the lexical in the pad.
S.
Well it didn't spring to my mind. I seemed more to mosey in.
I wonder about Ponie though. I don't think Parrot plans to support Perl
5 without help from perl5. However, I don't know enough about Ponie to
know whether it will keep its refcounting around to help us out in that
case.
Luke
There are a lot of partial solutions. I get the impression that when
they say "timely destruction" they want a full solution. Here's an
example of a case that most partial solutions won't catch:
my %data;
sub a {
open my $fh, ">somefile";
$data{fh} = $fh;
}
sub b {
%data = ();
}
a();
# ...
b();
> I thought C++ only guaranteed destruction (on return or exception) for
> objects which were directly on the stack.
That's true, you have to explicitly delete most memory. I was actually
referring to the template refcounting classes that I use all the time:
void foo() {
ref<Image> image = new Image("somefile.jpg");
image->draw();
// image will be cleaned up automatically
}
Here, C++ manages only to refcount objects (or rather, pointers) that
are declared using ref<> instead of *. In a dynamically typed VM this
isn't possible.
Luke
Yes, I understand that. So, moving on...
> The example you described destroyed a ref within a sub, and then assumed
> that the object refed would be destroyed at scope exit.
I'm assuming you're referring to my Perl example, which you tactfully
omitted in order to lose the casual reader. Very cunning. :-)
Here it is again:
my %data;
sub a {
open my $fh, "> somefile";
$data{fh} = $fh;
}
sub b {
%data = ();
}
a();
# ...
b();
And what I mean is that most approximate solutions won't destroy $fh in
time. One example of such a proposal is keeping tabs on objects in the
current scope which need timely destruction and then do a sweep whenever
we lose a reference to one. Another is using generational schemes.
Generational schemes are good for finding *enough* dead objects quickly
when we run out of memory, but they're not good for finding whether a
particular object is dead.
However, Perl 5, since it uses refcounting everywhere, has no problem
with this. Parrot has decided not to use refcounting at all (a decision
that I think I agree with), and so we have this problem.
> This is incorrect: the object refed should be destroyed when the ref is
> destroyed in a general refcounting GC system.
And I don't understand what you mean here.
> Anyway, this led me to the (useful?) thought that you could delink the
> concept of timely GC and destruction at scope exit by registering
> atexit() or atleave() code on a scope (like finally{}) which explicitly
> undef'd registered PMCs. That way you would be able to guarantee
> destruction even if the PMC was still ref'd, which is probably more use
> for things like critical section locks than just relying on a
> side-effect of the GC, and is certainly easier to implement since it's
> just a flag on the lexical in the pad.
Precisely! But if we have to support this area of Perl 5 (I'm not
certain that we do yet), we have to implement its semantics
backward-compatibly. The programmer of Perl 5 relied on the side
effects because they worked then. So we're not allowed to break them.
And because of Perl 6's many lexical scope hooks, I hope that we can get
rid of any timely destruction policy, to encourage the use of lexical
hooks instead of destruction rules.
No matter how we slice it, it would royally suck to add a sweep at the
end of every lexical scope.
Luke
On Fri, 2005-01-14 at 17:52 -0700, Luke Palmer wrote:
> Shevek writes:
> > The example you described destroyed a ref within a sub, and then assumed
> > that the object refed would be destroyed at scope exit.
>
> I'm assuming you're referring to my Perl example, which you tactfully
> omitted in order to lose the casual reader. Very cunning. :-)
My mistake, I was too heavy with [del]. I meant to put it back in.
> Here it is again:
>
> my %data;
> sub a {
> open my $fh, "> somefile";
> $data{fh} = $fh;
> }
> sub b {
> %data = ();
# $fh is destroyed here, not at scope exit
foo();
bar();
# not here.
> }
> a();
> # ...
> b();
>
> And what I mean is that most approximate solutions won't destroy $fh in
> time. One example of such a proposal is keeping tabs on objects in the
Timely destruction in C++ (I think the only language we have mentioned
which _officially_ implements it) only happens at scope exit. "Timely
destruction" in Perl5 happens (probably) kind of when the ref goes away,
although I don't think even that is guaranteed, my memory of temporaries
and targets is too fuzzy.
> current scope which need timely destruction and then do a sweep whenever
> we lose a reference to one. Another is using generational schemes.
> Generational schemes are good for finding *enough* dead objects quickly
> when we run out of memory, but they're not good for finding whether a
> particular object is dead.
But they may be adequate in a pinch for handling many simple cases of
timely destruction. However we are likely to dig the same hole as perl5
where programmers rely on the behaviour [see bottom].
> However, Perl 5, since it uses refcounting everywhere, has no problem
> with this. Parrot has decided not to use refcounting at all (a decision
> that I think I agree with), and so we have this problem.
>
> > This is incorrect: the object refed should be destroyed when the ref is
> > destroyed in a general refcounting GC system.
>
> And I don't understand what you mean here.
The example above demonstrates.
> > Anyway, this led me to the (useful?) thought that you could delink the
> > concept of timely GC and destruction at scope exit by registering
> > atexit() or atleave() code on a scope (like finally{}) which explicitly
> > undef'd registered PMCs. That way you would be able to guarantee
> > destruction even if the PMC was still ref'd, which is probably more use
> > for things like critical section locks than just relying on a
> > side-effect of the GC, and is certainly easier to implement since it's
> > just a flag on the lexical in the pad.
>
> Precisely! But if we have to support this area of Perl 5 (I'm not
> certain that we do yet), we have to implement its semantics
> backward-compatibly. The programmer of Perl 5 relied on the side
> effects because they worked then. So we're not allowed to break them.
I maintain that relying on those semantics is a clear violation of the
documentation, as explicitly stated somewhere I can't immediately find,
but someone else must be able to find the passage. Such code may be
considered broken. I also suspect it's not a hugely common case.
> And because of Perl 6's many lexical scope hooks, I hope that we can get
> rid of any timely destruction policy, to encourage the use of lexical
> hooks instead of destruction rules.
Yes. Lexical hooks good.
> No matter how we slice it, it would royally suck to add a sweep at the
> end of every lexical scope.
I wouldn't worry too much about a last-generation sweep at that point,
especially if we know which zones are in scope (as opposed to extent) at
that time. However, I doubt anyone is going to implement this.
S.
If yes, then you could also use an explicit construct such as C++'s
auto_ptr<> & the likes (read: an "auto" declaration), C# using()
mechanism (read: a "block statement" thing which gets expanded to the
perl equivalent of foo = ...; try { ... } finally { foo.Dispose(); }
or simply implement a using function (or the more general
UNWIND-PROTECT, as in CL).
Latter would have the advantage of not requiring extra syntax.
- Michael
> You could adapt Java's last-generation GC scheme to do a really fast GC
> on scope-exit, only of objects created within that scope. However, this
> may require a relocating or treadmill GC to do efficiently.
Yeah. That's what I'm currently working on:
$ l src/gc_gms.c
-rw-r--r-- 1 lt users 9188 Jän 15 10:07 src/gc_gms.c
Keywords:
- non-copying, mark & sweep
- generational
- implicit reclamation, treadmill
> seem to remember a zone-based GC strategy which allocated 'zones' to
> frames and GC'd by zone
Yes. In the presence of PMCs with timely destruction, each lexical scope
will be one GC generation.
leo