1. 27
  1. 3

    The note on Python’s garbage collector is interesting. I had been wondering recently if any languages performed limited* lifetime analysis to deallocate objects that were obviously dead meat. Does anyone know of others?

    *Limited compared to Rust’s lifetime analysis.

    1. 3

      Go doesn’t have generational GC and leans a bit on escape analysis to change heap allocations to stack allocations with a defined lifetime. There are limitations: dynamic calls (like anything using interfaces in Go) are opaque to the compiler so it can’t analyze across them. I think variable-size allocations don’t take advantage of EA now, because it always moves things to the stack and the runtime wants to be able to make simple stack maps.

      I think Python gets most garbage through refcounting; you just need to GC to catch reference cycles when two objects point at each other and so on.

      1. 3

        Fun thing, you can turn the GC off in Cpython 2.x (and I think in 3.x too but I never bothered to check). In that mode, data structures which don’t have any cycles are still managed correctly by refcounting, but cyclic structures will cause objects they refer to to hang around forever.

        This isn’t usually recommended to anyone because it’s likely that almost none of the libraries you use have been tested for leaks in this mode.

        There was a story here a year ago about Instagram doing this, for slightly esoteric reasons. https://lobste.rs/s/59rwdm/dismissing_python_garbage_collection_at

        And there was a follow up a while later which is now at the top of my to-read pile. Not linking the lobsters thread on that one because it sucked. https://engineering.instagram.com/copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf

        1. 3

          *Limited compared to Rust’s lifetime analysis.

          In Rust, object lifetimes are tied to their scope, unless they’re heap-allocated in which case they’re cleaned up via RAII (also scope-based) or dynamically (reference count goes to 0). “Lifetime analysis” in Rust probably can only mean lifetime constraint checking which doesn’t have anything to do with deallocating objects.

          1. 2

            I believe that you are correct. I was not familiar with the term escape analysis before my post, but it looks like that was the idea I was thinking of. I’m reading through the rust book right now and assumed that a weaker version of lifetime constraints could be applied to languages which do not have as rigorous of deallocations rules.

            To further clarify what I meant by “limited”, I was thinking of how lifetime annotations are sometimes required in rust functions. I was wondering how far you could apply lifetime constraint rules without changing the semantics of a language.

            1. 2

              I was not familiar with the term escape analysis before my post, but it looks like that was the idea I was thinking of

              Right, that makes sense. Escape analysis can certainly lead to tightening up the lifetime of objects.

              I was wondering how far you could apply lifetime constraint rules without changing the semantics of a language.

              It’s possible that I don’t understand precisely what you mean here, but I want to point out that lifetime annotations don’t really change the semantics even of Rust - that is, if you could switch off the borrow checker in the Rust compiler, so to speak, it should still be able to generate exactly the same code (it would just lose the ability to catch certain errors, and lose memory safety as a consequence, though a correct Rust program would still run without memory-safety issues).

              That “lifetime of objects is tied to scope” in Rust is caused by objects being stack-allocated. Languages that heap-allocate all objects by default have much more capacity, theoretically, to perform static analyses allowing them to determine when object lifetime could really end - possibly before the stack frame is released, possibly at the same time, possibly earlier; in the latter two cases, a compiler (including a JIT) could elect to allocate the object on the stack instead (but it could also just insert a heap deallocation at the appropriate juncture).

          2. 2

            For several versions, Hotspot’s JIT compiler has done escape analysis and used it to stack allocate objects that will not escape.

          3. 2

            Roberto Ierusalimschy’s keynote at Lua Workshop 2018 was about the evolution of Lua’s GC (slides).

            tl;dr until Lua 5.0 the GC was a basic stop-the-world mark-and-sweep. In Lua 5.1 it became incremental, and in Lua 5.2 it became generational, with objects being considered old when they had survived one GC cycle. In 5.3 that was changed to two GC cycles.

            Note that the slides include the phrase “most objects die young” but despite that I am certain Roberto is aware of the actual behavior. :)

            1. 1

              I’m sure he is.

              The question is how aware the people reading the slides are of the actual behavior.