8 ms·
I develop languages full time, and it's clear to me that RC will make massive strides forward in the next decade, for a few reasons: 1. First-class regions all
by verdagon 4y ago
I develop languages full time, and it's clear to me that RC will make massive strides forward in the next decade, for a few reasons:
1. First-class regions allow us to skip a surprising amount of RC overhead. [0]
2. There are entire new fields of static analysis coming out, such as in Lobster which does something like an automatic borrow checker. [1]
3. For immutable data, a lot of options open up, such as the static lazy cloning done by HVM [2] and the in-place updates in Perceus/Koka [3]
Buckle up yall, it's gonna be a wild ride!
[0] https://verdagon.dev/blog/zero-cost-refs-regions https://verdagon.dev/blog/zero-cost-refs-regions
[1] https://aardappel.github.io/lobster/memory_management.html https://aardappel.github.io/lobster/memory_management.html
[2] https://github.com/Kindelia/HVM https://github.com/Kindelia/HVM
[3] https://www.microsoft.com/en-us/research/publication/perceus-garbage-free-reference-counting-with-reuse/ https://www.microsoft.com/en-us/research/publication/perceus...
- nu11ptr 4y agoThis. Said much better than my other posts, but this is exactly what I was trying to say. Naive ARC vs GC... a good GC will kill it, so don't make the ARC naive...cheat and find ways to skip most ref. counter bumps. It became amazingly clear when I started coding Rust: you only have to bump ref count when you get a new owner. Most RC objects die with just a few counter bumps. Most objects don't need RC at all (most objects can be single owner). Now imagine a lang (lobster given as an example above) that does this automatically. Sweet spot.
- nu11ptr 4y agoTwo more interesting ways to speed it up: - Make threads a "special" tracked API call (for example, like Rust) so that you know when something will or won't be passed between threads. If it never "escapes" the thread, you can use non-atomic ref counts for the lifetime of that object (NOTE: I should add this is something I've been thinking on - I don't know how hard/feasible it is to do in practice) - Use biased RC - don't have link to paper handy, but idea is that most multithreaded objects spend most of their life on one thread and when they are on that thread, can use non-atomic ref counts by using a separate counter. When on any other thread they use an atomic counter. When both counts hit zero, object is destroyed.
- verdagon 4y agoDefinitely! Vale, Lobster, and Cone all require explicit annotations for anything that could cross thread boundaries. Vale's design bends the rules a little bit, it blends structured concurrency with region borrow checking to allow things to temporarily escape the current thread. [0] I believe Lobster spawns a new VM per thread, and has some special abilities to share data between them, but I'm vague on the details. Cone requires an explicit permission to allow crossing thread boundaries, which I think will work pretty well. [1] I've read about biased RC before, but have been skeptical because of the branching involved. How has it worked out in practice? [0] https://verdagon.dev/blog/seamless-fearless-structured-concurrency https://verdagon.dev/blog/seamless-fearless-structured-concu... [1] https://cone.jondgoodwin.com/safety.html https://cone.jondgoodwin.com/safety.html
- nu11ptr 4y agoI've just read about it, never actually used it verbatim tbh, but just found it kinda fascinating. I wrote a somewhat modified version (I don't track local thread ID, I use Rust's type system and Send/Sync properties instead) of the algorithm for a very different purpose: I use it to allow safe moves back and forth between (non-atomic) Rc and (atomic) Arc objects. For this purpose, it is very useful. The actual Rc/Arc operations are no more expensive than regular Rust Rc/Arc (except for the last decrement on Rc - slightly more expensive) I got the idea from this person (I didn't use their algorithm though - they probably do it similar to how I do it though): https://crates.io/crates/hybrid-rc https://crates.io/crates/hybrid-rc
- rfoo 4y agoBiased GC is implemented the "nogil" Python fork. It worked okay-ish.
- elcritch 4y agoNim's ARC memory management uses the first strategy. Non-atomic refs combined with moves really speed things up! Moving data across threads requires the data be of a special type `Isolate`. It can be a bit tricky to prove to the compiler that the data is isolated, e.g. single ownership.
- thesz 4y ago"Garbage collection can be faster than stack allocation" is actual title of this paper: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.2064&rep=rep1&type=pdf https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84... However non-naive your automatic counting scheme can be, it still has to touch object at least twice. However non-naive your automatic counting scheme can be, it still has twice as much overhead per object compared to GC - structure and count vs just structure. Sometimes you can factor structure out - by saying "the allocator page contains pointer to structure at the very start", - but this can be done for GC too. I guess in that case GC wins even more. And you need structure (virtual methods table, actual object structure, etc) because release of object should decrease counts of referenced objects. Finally, reference counting can and will fragment heap.
- astrange 4y agoHeap fragmentation is a real issue, but "GC is fast if you throw physical memory at it" is actually a very bad argument for it on personal computers, especially if they have to fit on your wrist, and especially if you've invented technologies that make scanning memory more expensive like swap since 1986. ObjC/Swift have some tricks to avoid allocating refcounts, mainly relying on storing them in unused pointer bits.
- thesz 4y agoOn the other hand I would like to present to you an LMAX Disruptor: https://lmax-exchange.github.io/disruptor/ https://lmax-exchange.github.io/disruptor/ Software based on this intra-program message bus usually follows "throw memory at it". That software is written in Java (hence GC) and process millions of transactions per second. The memory amount is so high that GC is not triggered at all during operations hours. Nevertheless, this is not achevable with refcounting because, well, refcounting has to keep counts. Refcounts, having non-local access patterns, do not play well with caches, let alone something that have much higher latency like swap. Garbage collection can be used to improve locality: https://safari.ethz.ch/architecture/fall2017/lib/exe/fetch.php?media=p332-chen.pdf https://safari.ethz.ch/architecture/fall2017/lib/exe/fetch.p... Refcounts have troubles with cyclic structures. I guess these tricks of refcount allocation avoidance will work fine unless there's a scale-invariant graph with several tens of thousands nodes. In scale-invariant graphs there is some number of nodes that have links from almost all other nodes. Actually, I've got exactly that experience: typical weighted finite state transducer's HCLG graph for automatic speech recognition is scale-invariant. Analysis over it (in C++ using OpenFST) usually was done reasonably quickly, but there was a substantial delay between final printf and actual program exit, when all these pescy refcounted smart-pointed structures finally got freed. I write all that for other readers to have more unbiased view of the problem. As for me, I greatly prefer arenas - much like "automatic regions with reference counting" mentioned above. Arenas allow for cyclic structures, easy to implement even in C and allow for garbage collection algorithms.
- amelius 4y ago> Naive ARC vs GC... a good GC will kill it, so don't make the ARC naive...cheat and find ways to skip most ref. However, what if you applied that same principle to a GC'd language ...
- sillysaurusx 4y agoEhh. Who cares, though? And I mean that in the least dismissive way. Why do you care? Sell me on it. GCs have gotten to the point where the slowdown is almost the butt of jokes. LuaJIT proves that it can get damn close to C speeds. What you’ve said sounds like a whole lot of complexity for … what? Give me the automatic threading speed improvements with no extra programmer effort and I’ll change my stance 270 degrees.
- nu11ptr 4y agoFor me the biggest benefit is deterministic destruction, specifically for the non-memory resources it is holding (files, locks, etc.) the same as if it was stack allocated. It is very freeing knowing that is just being taken care of. No 'defer', 'with' or other special handling needed. But possibly the bigger benefit: no heap allocation for many objects at all. If you can prove single ownership, you can stack allocate it (or "inline it" if in a struct/object).
- shakna 4y ago> No 'defer', 'with' or other special handling needed. Just as an aside - in Python, it surprised me that 'with' is _not_ a destructor. The object created will remain in-scope after the 'with' body, but the '__exit__' method will be called. Which for the canonical example of file-handling, only really closes the file.
- jnwatson 4y agoThe done vs destroyed dichotomy is a cross-cutting concern for programs that need explicit control over resource lifetimes. RAII is actually an antipattern in Python if you actually care about resources being released.
- shakna 4y agoThe surprise is less about resource usage and more about the scope lifetime. 'with' introduces a new binding that outlives its body. It looks, at first glance, to be a temporary variable. But it isn't.
- osaariki 4y agoThe in-place update work in Koka [1] is super impressive. One of my co-workers, Daan Leijen leads the Koka project and hearing his talks about it have been such a privilege. The work around Koka is really convincing me that functional languages will eventually lead the pack in the effort-to-performance trade-off. Something that came out of the Koka project that everyone should know about is mimalloc [2]: if your built-in malloc is not doing it for you, this is the alternative allocator you should try first. Mimalloc is tiny and it has consistently great performance in concurrent environments. [1]: https://koka-lang.github.io/koka/doc/index.html https://koka-lang.github.io/koka/doc/index.html [2]: https://github.com/microsoft/mimalloc https://github.com/microsoft/mimalloc
- ashton314 4y agoOoooh… Koka is a really neat language. I first encountered it in a seminar when we talked about algebraic effect handlers. Koka has these row types which effectively allow you to statically ensure that all your effects get handled at some point, modulo certain ordering constraints on when those effects get handled. Essentially, you get nice purity around effects but they're way easier to compose (and imo grok) than monads. If anyone is interested in learning more, definitely take a look at this [1] paper by the aforementioned Leijen. (OP, very cool that you get to work with this guy.) One of the best-written papers I've seen. [1]: https://www.microsoft.com/en-us/research/publication/algebraic-effects-for-functional-programming/ https://www.microsoft.com/en-us/research/publication/algebra...
- staticassertion 4y agoI'm really excited for koka. I still need to dig in and actually try writing some code but it looks so promising. At a minimum I hope that it inspires other languages to look at these features. There's definitely bits I don't love, but they're nothing I couldn't get over.
- nsm 4y agoI actually attempted some of 2021's Advent of Code in Koka. It was fun for the first couple of questions, but then got really painful for a couple of reasons. None of this is a complaint on the project, as it is still a research language and has not had a lot of effort invested into getting others to use it. What I really liked was how easy it was to write parsers due to the effect system. i.e. you'd write a parser that looked like a direct imperative translation of the BNF grammar or the problem statement, and then the effect system would propagate the actual input through the call graph and each parser would only have to deal with its small part. However several things made me get frustrated and stop which wouldn't have happened with better documentation and more source code to look at. As someone who has no theory background in effect systems, the syntax was very oblique. i.e. when do I need to mark that my function needs to have a `| e` effect or a `, e` effect. I'm not a fan of the Haskell style of introducing single-letter free variables without any syntactic convention. For example, in Rust, the tick is used to clearly mark that something is a lifetime annotation. The rules about how lifetimes propagate, and so when you need to introduce 2 or more lifetimes are explained clearly (if not in the Rust docs, then somewhere else on the internet). That wasn't the case for Koka's effect syntax. In addition, I would sometimes stumble onto effect annotations that should've worked according to my limited understanding, but didn't. The compiler errors in such cases were really meaningless to me. Other times, I feel like the errors almost indicated that what I was doing was semantically correct, but was not yet implemented in the compiler. Also apparently the `_` has special meaning at the beginning of an effect annotation, but I only found this out from trial and error. Eventually I would just give up on annotating any functions until the compiler complained, but this made it difficult for me to understand my own code (it was like writing Python without type annotations). The API docs are mostly just type signatures, which means unless it is some simple function, or it is used in the koka implementation sources, you are left scratching your head about how to use the standard library. Things like `ref()` types could also use much more elaboration. Also, at the end of the day, Advent of Code solutions are generally pretty small scripts, where there isn't a need for effect systems. It was often easier to just use Python and its solid standard library. Overall, it is an interesting language and I'll definitely keep an eye on it. My very limited code if anybody is interested: https://github.com/nikhilm/AdventOfCode2021 https://github.com/nikhilm/AdventOfCode2021
- bmitc 4y agoWhat's the state of implementations suitable for real-time systems?
- deleted 4y ago[deleted]
- forrestthewoods 4y agoFascinating. I’ll be honest, I’m moderately skeptical! Various flavors of GC has caused me great pain and suffering. The promise has long been “GC, but less bad” and so far that has not come to fruition. However, I’m ecstatic at how many languages are trying new and interesting things. The ecosystem of novel languages feel more rich and vibrant now than at any other point in recent memory. Probably due to LLVM I think? You should give a talk at Handmade Seattle in the fall! Would be a great audience for it I think.
- oikawa_tooru 4y agoHi, I am bootcamp grad currently working with fullstack ror development. I have started to get bored of this work and want to venture into other core cse fields. Compiler design and machine learning seems to have caught my interest the most. What would your advise to be someone who wants to break into this field by self study? Does compiler design involve any math that is also used in machine learning and deeplearning? And do they share any similar concepts?
- chaosite 4y agoML utilizes continuous mathematics, while compiler design including static analysis usually favor discrete mathematics. I wouldn't say the concepts are interchangeable. The classic text book is the Dragon Book. Another good start on the compiler side are projects like https://buildyourownlisp.com/ https://buildyourownlisp.com/. A lot of people start with lexing, parsing, and so on before moving on, but you can start from the static analysis side too. I would look at the LLVM framework and the Clang Static Analyzer, and use one of the tutorials for that to write a small analyzer for C.
- oikawa_tooru 4y agoThank you so much for the guidance!! I will look into it soon!!
- simscitizen 4y agoMaybe start with a good book or two to see if you're actually interested. Crafting Interpreters (https://craftinginterpreters.com/contents.html https://craftinginterpreters.com/contents.html) might be a good intro--yes, it's about interpreters, but there's a lot of overlap between creating an interpreter and creating a compiler.
- kcartlidge 4y agoAs very much a non-expert in compiler design, I found two books to be helpful introductory reads (neither are free though). I'm not sure whether someone proficient in the field would recommend them as my knowledge in the area is small, but I enjoyed them. - Writing An Interpreter In Go by Thorsten Ball (https://interpreterbook.com https://interpreterbook.com) - Compiling to Assembly from Scratch by Vladimir Keleshev (https://keleshev.com/compiling-to-assembly-from-scratch https://keleshev.com/compiling-to-assembly-from-scratch) The first produces an interpreter in Go for a custom language, but as simscitizen says there's a lot of crossover with compilers. The second creates a compiler written in Typescript that compiles from a minimal form of Typescript into ARM assembly. It's not cheap but is good.
- LadyCailin 4y agoA little off topic, but may I ask how you managed to work on this full time? I am also developing a programming language, (methodscript.com if you’re curious) but I’ve never been anywhere near being able to work on it full time.
- eatonphil 4y agoTheir site mentions they worked for Google for 6 years. The stock alone from doing that would be enough to self fund for a while. They may also be getting grants or sponsors for their language or game development. Or they could be doing contract work of any kind to pay the bills. These are all common approaches to working on your own thing full time.
- verdagon 4y agoYep, I worked at Google until they silently started sunsetting all the small endeavors and funneling everyone into Ads/Cloud/Search. Back in 2014, the internal position listings were full of fascinating projects that could bring a lot of good, and looking at it now, it's pretty clear the writing is on the wall for the culture. I left late last year. Luckily, I've saved up enough to be able to work on Vale full-time for a year or two, hoping I'll have enough sponsors by then to be able to continue. If I play my cards right, I'll be able to make Vale into a nonprofit, a la ZSF. A worthy endeavor!
- zozbot234 4y agoNote that Rust will most likely be getting some support for these features too, at some point. It's what much of the academic PL research on borrow-checked languages is pointing towards. You can alreasy see some of the practical effect with "GhostCell" and similar proposals, even though these are arguably much too clunky and unintuituve for practical use (notably, they use obscure features of the Rust lifetime system to implement their equivalent of regions). Language-level support is likely to be rather easier to work with.
- NeutralForest 4y agoHey thanks for mentioning Lobster, I didn't know about it and it looks cool, I'll probably try to draw a couple things with it =)
- panick21_ 4y agoThere is also a lot still to do on the GC side. Azul GC with hardware support allowed for amazing results. Even without hardware support the results are quite good.
- rectang 4y agoIf you work on software that has hard real-time constraints, e.g. live audio processing, do any of the improvements to GC matter? GC reminds me of satellite internet: no matter how fast it gets in terms of throughput, the latency will always be unacceptable — but because the latency issue can't be solved, advocates will keep trying to tell you that latency doesn't matter as much as you think it does and your problem isn't really a problem. (All sorts of bad experiences in modern software come down to somone trading away latency to get throughput.) Rather than the framing of "GC vs. ARC", I'm mostly interested in improvements to the ownership model.
- verdagon 4y agoIronically, even though I believe RC will make great strides, I actually agree with you. Single ownership has the most potential, especially if we decouple it from the borrow checker as we are in Vale. [0] Being able to control when something is deallocated is quite powerful, especially for real-time systems. [0] https://verdagon.dev/blog/generational-references https://verdagon.dev/blog/generational-references
- pjmlp 4y agoThere are real time GCs when one is willing to put the money on the table. https://www.ptc.com/en/products/developer-tools/perc https://www.ptc.com/en/products/developer-tools/perc https://www.aicas.com/wp/products-services/jamaicavm/ https://www.aicas.com/wp/products-services/jamaicavm/
- Tozen 4y ago> "GC reminds me of satellite internet." > GC reminds me of satellite internet: no matter how fast it gets in terms of throughput, the latency will always be unacceptable — but because the latency issue can't be solved, advocates will keep trying to tell you that latency doesn't matter as much as you think it does and your problem isn't really a problem. LOL, like this statement, and will have to borrow it for future discussions/debates.
- kazinator 4y agoA simple (obj &val) parameter in C++ eliminates reference counting by lending a reference, which can go an entire tree of helper functions.
- KMag 4y agoI briefly scanned the Lobster reference, but I didn't see how they're dealing with the undecidable cases. Presumably it punts out to the garbage collector for objects where lifetime isn't statically decidable, right? My understanding is the answer for "Why doesn't Rust use something like type inference for lifetimes?" is "In the general case, that reduces to the halting problem."