incubator-lucy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Michael McCandless <>
Subject Re: Reference counting inside a GC host (was "real time updates")
Date Wed, 25 Mar 2009 12:08:32 GMT
Marvin Humphrey <> wrote:

>> So... one alternative would be to separately track a private-to-Lucy
>> refCount from the host object's refCount?
> Since you refer to the "host object's refCount", I take it the following
> passage applies to refcounted systems like Perl and Python, and not to GC
> systems like Ruby and Java:

Sorry, let me refine the proposal: let the host GC the host object,
and Lucy GC's the C object, and cross-link (circular ref) the firt
time the bridge is crossed.

I think Java (JNI) actually exposes a refCount-like API, ie you can
say "I (C) am referring to Java object X", which I think just adds a
new global ref that the tracing collector includes.  Presumably Ruby
exposes something similar?  And RefCount host languages obviously let
you incRef/decRef.

>> Then, for Lucy objects that
>> never cross the bridge you wouldn't have to make a "false" host
>> object.  But you'd need to take care to destroy an object when both
>> Lucy's Obj & the host's wrapper obj drop to refCount 1.
> OK, if I understand you correctly, then there would be two refcounts.
> In essence, we intentionally create a circular reference between the Lucy
> object and the host wrapper object.  However, we avoid the memory leak because
> whenever either of the two refcounts falls to one, we see whether the other
> refcount is also at 1 -- and if that's the case, we destroy the object.
> The problem I see here is that we don't necessarily have control over what
> happens during the host refcounting.  In Perl at least, I don't know of a way
> to override what happens during SvREFCNT_dec, because it's not a method.  So
> we can't set up a trigger that fires when the Perl object wrapper's refcount
> falls to 1 -- the only event we can count on is the call to $obj->DESTROY()
> when the perl reference count falls to 0.

Hmm, right, that's a problem.... I think Python also doesn't let you
override incRef/decRef for an object.

Maybe you could not keep a permanent ref to the host object?  Eg say a
Term crosses the bridge; at that point you could make a host object
for it, but when the term comes back across the bridge, remove that
reference.  If the host language keeps a ref and later crosses the
bridge backwards, it would hold a ref to the Lucy obj.

Ie things that make a stack-only foray into the host language would
get a temporary host object wrapper; sort of like autoboxing.

I haven't really though through the implications of that though...

>> This may also be better for non-refCount languages (Java).
> How would maintaining a refcount for a Java object work?  Under a tracing GC
> system, I think you could say that the refcount is "true" until the object
> becomes unreachable, at which point it's "false".  Other than the fact that
> the object is reachable, you don't know how many things are pointing at it.
> Because of that, there's no way to detect when the only reference left is the
> one from the Lucy object.

Sorry, I meant you'd use JNI's API to say "I am now referring to java
object X" and "I have stopped referring to java object X".  Basically,
a refCount.  But we still have the same tight-cycle challenge.

>> > However, that's not a major problem unless we're creating and
>> > destroying a boatload of small objects.
> ---->8 snip 8<----
>> I think Lucene has improved here too, especially on the indexing side
>> (though the searching side doesn't create too many tiny objects I
>> think).
> Nothing like indexing.  The only spot I can think of is the term dictionary
> index, with all those Term and TermInfo objects.

Yeah... LUCENE-1458 addresses that (no more TermInfo & Term instances
created for the terms index... though it's still creates String

> In Java, you could address that by turning a 1000-object array of 5-element
> TermInfo objects into 5 primitive-type arrays with 1000 slots each.  We want
> to do something like that anyway because of flexible indexing.

Right, column stride instead of row stride.

> In KS, we no longer generate a lot of Term/TermInfo objects because that data
> structure has been modified for use with mmap.  However, we'll want to mod
> that system further for flexible indexing and PFOR.

Right, LUCENE-1458 takes some similar steps forward -- it stores less
in RAM, eg we don't need most of the TermInfo for each indexed term;
just the long pointer into the tis file.

> I look forward to taking that up in the future.  :)

There's alot to take up in the future!!

>> > literals, and such in a bootstrap routine, but it's enough of a pain
>> > to set something like that up that I haven't gone and made such a
>> > change in KS.
>> >
>> > I'd really like to kill of FastObj just for the sake of simplicity,
>> > though.
>> What objects are planned to subclass FastObj?
> VTable, plus the CharBuf family.
> Right now in KS, the following classes also descend from FastObj:
>    VArray
>    Posting
>    Token
>    Inversion (nee TokenBatch)
>    ByteBuf
>    ViewByteBuf
>    Undefined
> However, none of them have the declaration/bootstrapping problem presented by
> VTable and CharBuf.  Additionally, I hope to eliminate Token (by expanding the
> capabilities of Inversion), Posting is due for an index-time overhaul but
> isn't a problem at search-time, Undefined isn't important, and ByteBuf and
> ViewByteBuf don't present speed problems.
> Rebasing VArray and Inversion (which is a subclass of VArray) to be children
> of Obj rather than FastObj causes a performance dip of about 3% on the
> indexing benchmark.  I imagine that's as bad as it would be for any binding
> because Perl objects are pretty heavy.


>> > The Ferret scheme won't cause problems with light usage of the
>> > library, because most of Lucy's work will be done within tight loops
>> > in the C core.
>> What about a HitCollector in the host language?  Can you efficiently
>> re-use an object from the host language?  (Python has such tricks, eg
>> to re-use a TupleObject during iteration).
> Oh, definitely a HitCollector would be a problem.
> Restating the original assertion: the Ferret scheme won't cause problems if
> you avoid host-language subclasses.


>> > Do we need to
>> > add all new objects to a giant Hash that we tell the host about, and
>> > yank C stack vars out of that Hash before the C function returns?
>> What does Ruby's C-embedding API expose to interact with its GC?  I
>> would imagine it'd be similar to Java's (ie "here's a new global root
>> object")?
> I understand you can do something like that.


>> I don't understand the C stack vars / hash question.
> Consider the following routine, set up for tracing garbage collection:
>    void
>    FSFolder_delete(FSFolder *self, const CharBuf *filename)
>    {
>        CharBuf *fullpath = (CharBuf*)GC_add(
>            CB_newf("%o/%o", self->path, filename));
>        bool_t result = Host_callback_i(self, "do_delete", 1,
>            ARG_STR("path", fullpath));
>        if (!result) {
>            THROW("failed to delete %o", fullpath);
>        }
>        GC_remove(fullpath);
>    }
> Assume that the CharBuf formatted constructor CB_newf() caches a Host obj
> within the CharBuf, and that no reference counting is performed because we're
> going to rely upon the host's tracing garbage collector.
> When "fullpath" is created at the top of our function, it's unreferenced as
> far as the Host's garbage collector is concerned.  The only place it exists is
> on the C stack -- but the garbage collector doesn't walk the C stack looking
> for roots.  It walks the host's stack, but not the C stack.
> As soon as fullpath is born, it is a candidate for garbage collection, and
> could be reclaimed at any moment.  Therefore, we *have* to call GC_add() to
> inform the Host about our new CharBuf.

You're going to need to register all threads with the host language
for this reason?  Doesn't java need the threads to stop mutating
things, at least to get the roots for tracing?

> If we *don't* call GC_add(), we could potentially get an invalid read error
> while composing the message for THROW.  Say that Host_callback_i doesn't send
> "fullpath" itself into host-space, but instead copies its contents into a host
> string type.  (This is actually what happens in the current KS implementation.)
> Now, say that a GC takes place during Host_callback_i().  Uh-oh, "fullpath" is
> toast -- it was unreferenced, and got reclaimed.  If THROW needs it, we'll be
> reading from freed memory.
> Our only remedy is to call GC_add() early on, protecting fullpath until we
> know we don't need it any more.  But calling GC_add() means that we have to
> call GC_remove() somewhere, or we'll leak memory.
> That call to GC_remove() at the end was what I meant by "yank C stack vars out
> of that Hash before the C function returns".
> Now, it turns out that the same function set up for a refcounting host looks
> pretty similar... eliminate the call to GC_add(), and change the GC_remove() to
> a DECREF():
>    void
>    FSFolder_delete(FSFolder *self, const CharBuf *filename)
>    {
>        CharBuf *fullpath = CB_newf("%o/%o", self->path, filename);
>        bool_t result = Host_callback_i(self, "do_delete", 1,
>            ARG_STR("path", fullpath));
>        if (!result) {
>            THROW("failed to delete %o", fullpath);
>        }
>        DECREF(fullpath);
>    }
> Perhaps it's possible to somehow have the DECREF() op perform double duty and
> trigger GC_remove() on a garbage collecting host.  I dunno, that seems likely
> to cause double-free problems, but I'll try thinking that through in a future
> post.

This is devilishly tricky stuff!


View raw message