[HN Gopher] Chris Lattner on garbage collection vs. Automatic Re...
___________________________________________________________________
Chris Lattner on garbage collection vs. Automatic Reference
Counting (2017)
Author : Austin_Conlon
Score : 306 points
Date : 2022-04-23 23:14 UTC (23 hours ago)
(HTM) web link (atp.fm)
(TXT) w3m dump (atp.fm)
| pizlonator wrote:
| I agree with a lot of what he says.
|
| He gets something subtly wrong though: GC barriers are waaaaay
| cheaper than ARC, in the limit when you optimize them. Libauto
| had expensive barriers, but as Chris said, it's not super
| interesting since it was overconstrained. But the production
| high-perf GCs tend to have barriers that are way cheaper than
| executing an atomic instruction. JSC's GC has barriers that
| basically cost nothing, and that's not unusual. It's true that
| low latency GCs tend to have barrier overheads that are higher
| than high throughput GCs, but even then, the cost isn't anything
| like atomic inc/dec. The barrier overhead in RTGCs is often
| around 5-15% except for the esoteric algorithms nobody uses.
|
| Also, GCs trivially give you safety even if you race to store to
| a pointer field. ARC doesn't, unless you introduce additional
| overheads.
|
| Does what I say mean that ARC is bad? Heck no, ARC is super
| impressive. But it does mean that it's a good idea to weigh ARC
| versus GC as a determinism versus speed decision, and accept that
| if you chose ARC, you lost some speed.
|
| Oh but ARC also let's you do CoW better than what a GC could give
| you, so for some languages ARC is a speed win over any possible
| GC because it'll let you do fewer copies. I think that's true in
| Swift. But if you don't have CoW then GCs are def faster.
| vvanders wrote:
| I think his point about the amount of research done on ARC vs
| GC was interesting. Only recently have we see things like
| biased reference counting[1] that can significantly reduce the
| overhead of ARC.
|
| [1] http://iacoma.cs.uiuc.edu/iacoma-
| papers/PRES/present_pact18....
| [deleted]
| pizlonator wrote:
| Lol biased ref counting. I tried that years ago. It's super
| fast for serial workloads or very simple parallel ones. I
| would avoid it for anything real.
| pjmlp wrote:
| Reference counting is the oldest garbage collection
| algorithm, it has seen plenty of research.
|
| What keeps being overseen is the amount of engineering to
| make reference counting perform in modern hardware,
| effectively overlaps with other garbage collection
| optimizations.
| tialaramex wrote:
| > What keeps being overseen is the amount of engineering
| ...
|
| You probably wanted "overlooked" here. Even though "seen"
| and "looked" are similar in meaning, "overseen" and
| "overlooked" are not. This is a subtle but important
| distinction in English.
| https://grammarist.com/usage/oversee-vs-overlook/
|
| Historically you could choose "oversee" when you would
| today use "overlook" or vice versa, but these usages went
| from archaic to actively disapproved. One remnant though is
| you would still use "overlook" in a sentence like "Atop the
| hill, the old castle overlooks the whole town".
| waqf wrote:
| And yet (depending on how it is used) "oversight" often
| refers to an instance of overlooking, rather than of
| overseeing.
|
| It seems remiss of Grammarist to write an entire page
| without mentioning that.
| tialaramex wrote:
| Good point. Example showing the two contrasting meanings
| of the word "oversight" in modern English:
|
| At m.d.s.policy it was our view that Symantec management
| had inadequate _oversight_ of Symantec 's Certificate
| Authority and as a result on several occasions over an
| extended period an important _oversight_ relating to the
| name validation procedures at CrossCert, a Korean
| business authorised by Symtanec to issue on their behalf
| - occurred without being detected by Symantec. This
| definitely resulted in undetected misissuance.
|
| As a result of our efforts to fix this problem Symantec
| sold their entire CA business to DigiCert, and CrossCert
| became merely a reseller of DigiCert's products
| (including "Symantec" branded certificates)
|
| This meant that now DigiCert's trustworthy management
| exercised _oversight_ over all issuance of certificates
| from the new CA hierarchy even when the certificates had
| "Symantec" branding - while an _oversight_ by CrossCert,
| such as failing to verify the identity of a customer,
| would no longer cause misissuance because DigiCert would
| independently use the Ten Blessed Methods.
| celeritascelery wrote:
| In the article
|
| > The performance side of things I think is still up in the air
| because ARC certainly does introduce overhead.
|
| As you said, they can both introduce overhead, but the ARC
| overhead is way worse. Really the only reason to use garbage
| collection (which is really complex) over ARC (which is simple)
| is the performance and control you get. Otherwise it would not
| be worth the implementation cost. The question is not "up in
| the air" on performance; production-grade GC's are a clear
| winner. I would even be doubtful that CoW could make up the
| difference. But I would be interested to see data suggesting
| otherwise.
| nu11ptr wrote:
| No doubt straight up ARC with refcounting bumps on every
| function call,etc. vs a good GC with same allocation
| paths...a good GC will smoke it. No question.
|
| But that isn't how I read what Lattner said here, and I've
| been thinking similar things. Essentially with ARC the goal
| is to cheat (to be fair with tracing GC it is as well, just
| in different ways). And you cheat by implementing an
| ownership/borrow collector type system behind the scenes for
| getting rid of the need for RC on most of your objects. Once
| you do that, you have GC competing with stack allocation +
| some malloc/free scattered in. It becomes a much more
| interesting fight.
| adgjlsfhk1 wrote:
| note that GCs are starting to cheat too. Julia (for
| example) is in the process of implementing compile time
| escape analysis which will be used to (among other things)
| remove allocations as long as the compiler can prove you
| won't notice.
| hayley-patton wrote:
| Java, Go and JavaScript implementations do escape
| analysis already.
| gameswithgo wrote:
| adgjlsfhk1 wrote:
| cool! (I gave the example of Julia, but because it's the
| best GC implementation (it's not), but because it's the
| implementation I know).
| pizlonator wrote:
| This "cheating" has been going on in GC'd languages for
| decades. Java, JavaScript, and probably other language
| implementations have what they call "escape analysis".
| verdagon wrote:
| 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
|
| [1] https://aardappel.github.io/lobster/memory_management.html
|
| [2] https://github.com/Kindelia/HVM
|
| [3] https://www.microsoft.com/en-
| us/research/publication/perceus...
| panick21_ wrote:
| There 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.
| oikawa_tooru wrote:
| Hi, 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?
| verdagon wrote:
| That's great that you're interested in venturing into other
| fields! Switching fields every once in a while is a great way
| to grow and become a stellar engineer.
|
| If you have equal passion for those two choices, I would
| suggest leaning towards machine learning for now. An ML
| engineer can make $300k-500k/yr at the big companies after a
| few years.
|
| I left that world and started doing language development as
| an open source endeavor, and make only $700/yr [0] which
| isn't that much, though I'm oddly more proud of that than the
| salary I got from my years in Mountain View. I'm happier,
| being able to push the state of the art forward and show
| people what incredible potential there lies ahead in the PL
| realm. However, I'd recommend graduates to focus on finances
| for a few years before looking at what lies beyond, lest the
| financial stress overshadow the dream.
|
| I'm only about 110,000 lines into my journey with Vale, but
| so far I'd say there's little math. There's a lot of data
| structures knowledge needed to make a compiler fast, and some
| interesting algorithms, but not much overlap with ML. But who
| knows, maybe there's some cool things languages can do
| specifically for ML, that haven't been discovered yet!
|
| [0] https://github.com/sponsors/ValeLang and I'm grateful to
| all of them!
| oikawa_tooru wrote:
| Thank you Sir for your gracious response.
| eatonphil wrote:
| I'd encourage you to join the following subreddits that might
| be interesting to you: r/compilers, r/programminglanguages,
| r/EmuDev, r/databasedevelopment.
|
| You'll find more people like yourself interested in core CS.
|
| My company also runs a Discord community for those kinds of
| people: discord.multiprocess.io.
|
| Good luck!
| chaosite wrote:
| ML 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/.
|
| 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 wrote:
| Thank you so much for the guidance!! I will look into it
| soon!!
| simscitizen wrote:
| Maybe start with a good book or two to see if you're actually
| interested. Crafting Interpreters
| (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 wrote:
| As 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)
|
| - _Compiling to Assembly from Scratch_ by Vladimir Keleshev
| (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.
| rectang wrote:
| If 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.
| pjmlp wrote:
| There 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.aicas.com/wp/products-services/jamaicavm/
| verdagon wrote:
| Ironically, 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
| nu11ptr wrote:
| This. 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.
| amelius wrote:
| > 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 ...
| thesz wrote:
| "Garbage collection can be faster than stack allocation" is
| actual title of this paper: https://citeseerx.ist.psu.edu/vie
| wdoc/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.
| nu11ptr wrote:
| To be clear, I don't hate tracing GC, and in fact find all
| forms of GC fascinating. It is definitely "trade offs". I
| just happen to program in a space where I like the trade
| offs of ARC better than those of a tracing GC. This could
| change if I start doing different forms of coding, and in
| higher level languages I often do like a tracing GC, but
| Rust (and these new upcoming languages) are making me
| rethink this a bit atm.
|
| > However non-naive your automatic counting scheme can be,
| it still has to touch object at least twice.
|
| Only if the object is actually ARC. The whole goal is omit
| that entirely. Most objects live and die on the stack via
| proving single ownership. To be fair, I'm not sure why the
| same optimization couldn't be done for a tracing GC (many
| will say GC allocation and frees are already mostly 'free'
| because you compact live not dead objects, but by putting
| everything on the GC path, you force the need for a
| collection sooner than if it wasn't heap allocated at all).
|
| > Finally, reference counting can and will fragment heap.
|
| This is true, but in return we get deterministic
| destruction and easy FFI. If we can avoid a solid # of our
| allocations entirely this becomes less of an issue. When
| building things like trees which hit the allocator hard we
| use arenas, which are more like a traditional "tracing GC"
| minus the tracing (bump allocator + deterministic free when
| done, all at once).
| zozbot234 wrote:
| > I just happen to program in a space where I like the
| trade offs of ARC better than those of a tracing GC.
|
| Even ARC has to be augmented with some tracing in order
| to deterministically collect cycles. Ideally, of course,
| we would want to describe things like "non-cyclic
| ownership graph" as part of the type system, so that the
| compiler itself can decide where the addition of tracing
| infrastructure is necessary. (Delayed deallocation, as
| often seen in tracing, concurrent etc. GC, might be
| expressed as moving the ownership of finalized objects to
| some sort of cleanup task. But if you code carefully, the
| need for this ought to be very rare.)
| astrange wrote:
| Heap 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 wrote:
| On the other hand I would like to present to you an LMAX
| 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.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.
| verdagon wrote:
| RC might not fragment the heap for long! Someone built a
| defragmenting malloc called Mesh [0] which uses virtual
| page remapping to effectively merge two non-overlapping
| pages. Pretty brilliant stuff!
|
| [0] https://tiba-jrchang.medium.com/mesh-compacting-memory-
| manag...
| mananaysiempre wrote:
| > Finally, reference counting can and will fragment heap.
|
| Is there _anything_ that won't fragment heap and
| simultaneously doesn't insist on owning the world? It is
| really, really painful to allow references to cross the GC
| heap boundary in both directions (witness the GNOME Shell
| not-a-memory-leak), and to me that is the principal reason:
| in a low-level language, you can RC only a bit of stuff,
| but you cannot really moving-GC only a bit of stuff.
|
| (I don't expect an affirmative answer to this question as
| stated, but maybe some more basic abstraction for manual
| memory management would allow this?..)
| nu11ptr wrote:
| Two 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.
| elcritch wrote:
| Nim'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.
| verdagon wrote:
| Definitely! 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-
| concu...
|
| [1] https://cone.jondgoodwin.com/safety.html
| rfoo wrote:
| Biased GC is implemented the "nogil" Python fork. It
| worked okay-ish.
| nu11ptr wrote:
| I'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
| LadyCailin wrote:
| A 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 wrote:
| Their 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 wrote:
| Yep, 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!
| NeutralForest wrote:
| Hey 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 =)
| bmitc wrote:
| What's the state of implementations suitable for real-time
| systems?
| [deleted]
| sillysaurusx wrote:
| Ehh. 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 wrote:
| For 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).
| xxs wrote:
| >the non-memory resources it is holding (files, locks,
| etc.)
|
| Locks are entirely a memory construct. You can trivially
| write one, e.g. Java locks are written entirely in java.
| The files descriptors (incl. socket) - they should not be
| relied to be GC'd by default, they do require their own
| lifecycle. For instance: c# Disposable/ java's try with
| resource.
|
| >no heap allocation for many objects at all.
|
| This has been already the case for many a year - reference
| escape analysis, ref. escape elission, trace compilation.
| tialaramex wrote:
| Locks are, in fact, one of many resources your operating
| system can give you.
|
| Historically, in concurrent software your only option was
| to either use these OS allocated locks or build a spin
| lock from atomic operations (and thus burn CPU every time
| you're waiting for a lock). The OS allocated locks, such
| as from sem_open() or create_sem() _must_ later be
| properly released or you 're leaking a finite OS resource
| that will only be reclaimed on process exit or in some
| cases only at reboot.
|
| Today your operating system probably offers a lightweight
| "futex" style locking primitive, in which _so long as it
| 's not locked_ no OS resources are reserved it's just
| memory. But, you're not guaranteed that all your locks
| are these lightweight structures and even if you were you
| need to clean up properly because when they _are_ locked
| the OS is using resources so you need to unlock properly
| or you 'll blow up.
| xxs wrote:
| The only part needed to build a lock is to be able to
| 'sleep' the current thread and the latter to be able to
| be awaken, plus compare-and-swap or load linked/store
| conditional. There is nothing really special about the
| locks as long as the current thread can be made dormant.
| Technically a single OS lock (e.g. a futex), associated
| with the current thread would suffice to implement as
| many locks as one desires. The lock would be released
| when the thread dies in the clean up code.
|
| The locks that result in thread awaiting are still in-
| memory, of course. They are just OS provided primitives.
| Yet again, a well behaved/designed lock should be non-
| contested for the most calls (easily 99.9%+). In that
| case there is no mode-switch to get to the kernel. A
| contested lock that goes to the kernel is a context
| switch.
|
| In the end there is no need to do anything special about
| locks as they can implemented in the user-space unlike
| the file descriptors.
| tialaramex wrote:
| > In the end there is no need to do anything special
| about locks as they can implemented in the user-space
| unlike the file descriptors.
|
| If you get semaphores from create_sem() you will need to
| give them back, you can't just say "This shouldn't be an
| OS resource" and throw it away, exactly like a file
| descriptor it's your responsibility to dispose of it
| properly.
|
| Speaking of which, depending on the OS, your file
| descriptors might also just be implemented in user space.
| They're just integers, it's no big deal, of course you
| must still properly open() and close() files, the fact
| it's "implemented in user space" does not magically mean
| it isn't your problem to clean up properly.
|
| Likewise, if you opened a sqlite database, you must close
| it properly, you can't just say "Eh, it's only userspace,
| it will be fine" and throw away the memory without
| closing properly.
| xxs wrote:
| >If you get semaphores from create_sem() you will need to
| give them back...
|
| I don't quite get the continued argument - the created
| locks in the user space have 'zero' extra dependency on
| the kernel. In a GC setup they would be an object no
| different than a mere string (likely less memory as
| well). They can be GC'd at any time as long as there are
| no references to them.
|
| If you wish to see an example: java.util.concurrent
| package. ReentrantLock[0] is a classic lock, and there is
| even a Semaphore in the same package (technically not in
| the very same). Originated in 2004 with JMM and the
| availability of CAS. You can create as many as you choose
| - no kernel mode switch would occur.
|
| For the record I do no object that proper resource
| managed is needed - incl. sqldatabase close. The latter
| does use a file descriptor any time you establish a
| connection. However, locks need no special support from
| the kernel. Also in GC'd setup file descriptors would be
| GC'd in pre (or post) morterm via some finalization
| mechanism, however relying on the GC to perform resource
| management is a terrible practice.
|
| [0]: https://github.com/openjdk/jdk/blob/master/src/java.
| base/sha...
| [deleted]
| oaiey wrote:
| I would argue that resources need always explicit handling,
| no matter whether you do reference counting or GC. So if I
| do a one-liner to dispose an object or call delete on an
| object is the same beauty. Wait till the variable is out of
| scope together with many more or hit the GC cleanup is just
| not the right way to clean up externally resources in a
| proper way.
| shakna wrote:
| > 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 wrote:
| The 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 wrote:
| The 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.
| pphysch wrote:
| Consider the common pattern: with
| open(path) as file: result =
| func(file.read()) another_func(result)
|
| If `with` used a separate scope then it would be a lot
| less ergonomic. How might we bring data outside that
| scope and be confident that `with` isn't forcibly
| cleaning things up? Would be a lot more verbose.
| shakna wrote:
| I think you've missed what I've meant by a new binding.
| That the body executes is entirely intuitive.
|
| However: with open(path) as file:
| ... if file.mode == 'r':
| print("Huh? file is still a thing?")
| xscott wrote:
| Python doesn't have block scope, but most people don't
| seem to notice: def foo():
| if False: x = 123 print("x
| is visible out here:", x)
|
| That will generate an error because `x` is "referenced
| before assignment", but the variable exists for the
| entire function. This can bite you in subtle ways:
| x = "is global out here" def foo():
| print("all good:", x) def bar():
| print("not good:", x) if False:
| x = "now x is local, even before this declaration"
| ptx wrote:
| Everything is always* scoped to the entire function in
| Python. Perhaps that's not always ideal, but at least the
| rule is simple.
|
| * Well, not variables in generator expressions. There
| might be a few other exceptions.
| chombier wrote:
| I always wondered why does this variable leak outside the
| `with` scope, because this is _really_ the one place
| where it should not.
|
| IIRC python3 fixed scope leaks in list comprehensions,
| does anyone know why they didn't fix this one as well? Or
| does it have actual uses in practice?
| shakna wrote:
| I don't know the _motivation_ for why it leaks, but it
| leaks because the method calls of a context manager are
| __enter__ and __exit__, but not __del__. [0]
|
| The motivation is especially opaque, because though
| CPython does run __exit__ immediately, it isn't
| guaranteed to do so by the standard. So __exit__ will
| happen at some point, like __del__ with the GC, but it
| won't actually call __del__, which makes GC'ing the value
| more complicated as __del__ can't safely run until
| __exit__ does.
|
| I _think_ the motivation is something to do with __exit__
| being allowed to throw an exception that can be caught,
| whereas __del__ isn't allowed to do that, but I don't
| find it that convincing. And I've never seen an example
| where the introduced variable is ever referenced
| afterwards in Python's documentation (including in the
| PEP itself).
|
| [0] https://peps.python.org/pep-0343/
| chombier wrote:
| Thinking about it, my guess is that properly scoping the
| variable does not really prevent the user from leaking
| the value anyway: with context() as
| foo: leak = foo
|
| This is unlike list comprehensions, where it is
| syntactically impossible to bind the comprehension
| variable to a new name accessible outside the
| comprehension.
|
| So maybe the reason is that it is not really worth the
| trouble.
| SemanticStrengh wrote:
| What does it bring vs Java autocloseable?
| nu11ptr wrote:
| Java's "try with resources" is effectively the same as
| Go's 'defer' and Python's 'with', so the advantage is
| that you can't forget to do it. No matter what you do,
| when the object goes out of scope, the resource will be
| closed.
| pjmlp wrote:
| If you actually use static analysis, you can't forget it
| in Java either.
|
| And it isn't as if using languages without static
| analysis is still considered a best practice in the 21st
| century.
| tialaramex wrote:
| > And it isn't as if using languages without static
| analysis is still considered a best practice in the 21st
| century.
|
| So in summary, "Please just do better". Not helpful as a
| video game meme, and really no better for programming.
| pjmlp wrote:
| I guess that is why no one bothers with clippy then, it
| is superfluous.
| tialaramex wrote:
| Everywhere that a clippy lint is there to catch bugs, I
| wish it wasn't a clippy lint because it won't always get
| run and those bugs will get missed.
|
| An example of a clippy lint that I like in clippy:
|
| If you try to transmute() &[u8] into a &str clippy will
| point out that you should probably from_utf8_unchecked()
| -- this lint results in code that does the same _thing_
| but is clearer for maintainers about what we intended. If
| our reaction to the lint is "But it isn't UTF-8" then we
| just got an important red flag, but that's not why the
| lint is there.
|
| On the other hand, clippy lints for truncating casts and
| I want those _gone from the language_ so the warnings
| ought to at least live in the compiler not clippy. I
| would like to see a future Edition outlaw these casts
| entirely so that you need to write a truncating cast
| _explicitly_ if that 's what you needed.
| throwaway2037 wrote:
| Is Java escape analysis done at compile time or run time?
| I don't know.
|
| FYI: As I understand, escape analysis is frequently used
| to either (a) avoid allocating on the heap, or (b) auto-
| magically free-ing a malloc'd object from a tight scope.
| pjmlp wrote:
| First of all it depends on the implementation, the
| language specification doesn't state how it works.
|
| https://en.m.wikipedia.org/wiki/List_of_Java_virtual_mach
| ine...
|
| As it stands is mostly done at runtime and only for a).
|
| Otherwise you are better with try-resources, and for
| something like malloced memory there are off-GC heap
| APIs.
|
| Also Valhalla will eventually land, although it is
| getting long by now.
| vips7L wrote:
| Personally I wish they would move some escape analysis to
| compile time just to help with memory consumption. I'm
| assuming it would require byte code changes though.
| nine_k wrote:
| Think about the typical heap size for a GC-based language.
| It's usually 50-70% larger than a working set, to let GC run
| efficiently.
| throwaway2037 wrote:
| To be clear, in enterprise (non-embedded / non-mobile)
| programming, this is fine. Money (RAM) isn't the issue.
| Programmers with domain knowledge are much more expensive
| than adding more hosts or RAM. I am talking about the
| general case; I know HN loves to focus on "high performance
| / low latency", but that is less than 1% in "Enterprise
| Real World".
| iscoelho wrote:
| 1. LuaJIT can be faster than C due to optimizations regarding
| function calls.
|
| 2. GC is LuaJIT's greatest weakness [1] (It is _very_ slow) -
| Most high performance projects disable it and only use C FFI
| allocations.
|
| [1] http://wiki.luajit.org/New-Garbage-Collector
|
| LuaJIT 3.0 (which will have the new garbage collector) also
| has no ETA and is not in development.
| verdagon wrote:
| A valid question, happy to enlighten!
|
| To get the automatic threading speed improvements, a (region-
| aware) language might add a `parallel` keyword in front of
| loops, to temporarily freeze pre-existing memory, thus
| completely eliminating the overhead to access it. Vale is
| building this for its generational references [0] model but I
| hope that more RC-based languages attempt something like it.
| Can't get much easier than that ;)
|
| Lobster's algorithm doesn't actually incur any additional
| complexity. It's completely automatic.
|
| Perceus and HVM require immutability, which does require
| programmer effort, I admit. Pure functional programming just
| isn't as convenient as imperative programming for most.
| However, languages are learning to blend mutable and
| immutable styles, such as Pony [1], Vale [2], and Cone [3].
| When a language allows you to create an object imperatively
| and then freeze it, it's wonderfully easy.
|
| I think the next decade in languages will be about this exact
| topic: making it _easy_ to have fast and safe code. I think
| RC will soon bring some very compelling factors to that
| equation.
|
| [0] https://verdagon.dev/blog/seamless-fearless-structured-
| concu...
|
| [1] https://tutorial.ponylang.io/reference-
| capabilities/referenc...
|
| [2] https://vale.dev/guide/structs#mutability
|
| [3] https://cone.jondgoodwin.com/coneref/refperm.html
| MaxBarraclough wrote:
| I think the real point of their question was along the
| lines of _GC works great and these days it 's pretty fast,
| why research cleverer reference-counting?_
|
| Will these developments lead to appreciable performance
| improvements for 'typical' non-embedded, non-real-time
| programs?
| astrange wrote:
| The fastest system currently out there is iOS, which (as
| this article says) doesn't use GC and does use clever
| reference counting, so yes?
|
| Note, most people here arguing that GC is actually always
| faster are forgetting that reading memory itself has a
| cost, especially in systems with swap or other reasons it
| might have a /really/ large cost.
| Someone wrote:
| I think the consensus is that GC works great _if_ you
| throw enough memory at it (about three times what you
| really need)
|
| On mobile, the problem with that isn't as much that
| memory costs money, but that it costs Joules. Peak power
| usage of RAM may not be large compared to that of your
| screen, CPU, GPU, GPS, etc. but AFAIK, with current tech,
| memory keeps eating energy even if your device is
| sleeping.
|
| Being able to run with less memory, I think, is why
| iPhones can do with relatively small batteries and yet
| have decent stand by times.
| 12thwonder wrote:
| like Chris said in the article, it is difficult for GC
| languages to interop with C/C++ codebase.
|
| in addition, if your system requires precise control over
| when and when not to use CPU, like resource-intensive gaming,
| browsers, OS, then GC may not be a good choice.
|
| lastly, if you are using GPU via graphics API, I do not know
| any API that can garbage collect GPU memory but RC can
| naturally extend memory cleanup code to cleanup GPU memory as
| well quite easily.
| davnn wrote:
| CUDA.jl apparently uses Julia's GC to free up GPU
| resources, see
| https://cuda.juliagpu.org/stable/usage/memory/
| 12thwonder wrote:
| good to know, thanks!
|
| I personally would like to know when the GPU resources
| become available (freed) thus still inclined to prefer RC
| over GC tho.
| osaariki wrote:
| The 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
|
| [2]: https://github.com/microsoft/mimalloc
| staticassertion wrote:
| I'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.
| ashton314 wrote:
| Ooooh... 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/algebra...
| forrestthewoods wrote:
| Fascinating. 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.
| zozbot234 wrote:
| Note 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.
| benreesman wrote:
| I'm personally exhausted by these conversations and you should be
| too.
|
| ARC was/is a reasonable compromise: no other choice was obviously
| better. Legacy code is of supreme importance.
|
| But we have a new benchmark for best-in-class now: for all their
| insufferable Jehova's witness Jihad approach, and yeah it's
| annoying, the Rust people proved linear/affine in production.
|
| I find Carl and Yehuda's flagrant profiteering in the early days
| as unpleasant as the next thinking person, but affine/linear is
| game changing, and it's time to stop apologizing for why your pet
| thing can't do it.
| saagarjha wrote:
| None of the strategies you're claiming is the "best" really
| wins in every situation. There's inherent tradeoffs with all of
| them.
| munificent wrote:
| I think ref-counting is interesting, but I look at it as sort of
| like Level 3 self-driving cars where you _almost_ don 't have to
| pay attention but sometimes you _really_ do. That 's almost worse
| than Level 0 because humans are very bad at partial attention.
|
| With GC, assuming your program can accept the slight level of
| non-determinism and latency, you don't think about deallocation
| _at all_. There are just objects and references, and that is the
| extent of your mental model. That simplicitly frees up a lot of
| mental energy to focus on your problem domain.
|
| With ARC, you get determininstic deallocation and (maybe) better
| latency. Those are nice, and critical for some kinds of programs.
| And you _mostly_ don 't have to think about deallocation. Except
| that thanks to cycles, you can create _actual memory leaks_ where
| you have completely inaccessible objects that live in the heap
| forever. And, in order to avoid this, you have to know when and
| where to use weak or unowneded references. Once you throw
| closures into the mix, it becomes very easy to accidentally
| create a cycle.
|
| So ARC gives you more language complexity (strong, weak, and
| unowned references, closure capture lists, etc.). And there is a
| subtle property that you must always maintain in your program
| that is not easily found through static analysis. If you fail to
| maintain this property, you program may slowly leak memory in
| ways that rarely show up in tests but will cause real problems to
| programs running in the wild.
|
| Most of the programs I write don't need finalizers much and can
| afford a little latency. I relish being able to use closures
| freely even inside classes. For that kind of code, I strongly
| prefer tracing GC so that I don't have to worry about cycles _at
| all_.
| gary_0 wrote:
| In the case of C#, there are very lengthy docs and StackOverflow
| threads on how to properly use IDisposable -- what you need to
| use to clean up non-GC-able resources. A great many classes in C#
| libraries use IDisposable, so outside of pure business logic you
| end up with a lot of 'using' blocks. I've written a lot of C#
| code that needs to clean up external resources using Dispose(),
| and I've had to fix a lot of C# code written by people with a
| poor understanding of IDisposable and how GC actually works (eg.
| useless GC.Collect() calls everywhere).
|
| So I think the benefits of deterministic object lifetimes, and it
| being easy to explain when each object will get cleaned up (all
| of it, not just the memory), outweighs the marginal benefits of
| heavyweight Garbage Collection.
| 12thwonder wrote:
| this is a really good benefit of RC codebase.
|
| Resource management becomes much more harmonious and easy to
| follow.
| pjmlp wrote:
| There are Roslyn analysers for that.
| CyberDildonics wrote:
| What most people seem to forget about this is that if you
| actually need speed neither of these should be a factor. Heap
| allocations should be minimal and lifetimes should almost always
| be known ahead of time.
|
| In C++ you basically never need reference counting, unless you
| are handing off heap allocations to multiple different threads.
| In that case you don't know what will finish first and they need
| to communicate when the allocation isn't being used anymore.
| ncmncm wrote:
| The main thing you need to know about ARC is that you hardly ever
| need it, so it rarely matters what its performance is. If you
| find yourself using shared_ptr much, _stop_.
|
| Also, its susceptibility to cycles doesn't matter, because it is
| a terrible way to manage graph nodes: just keep them in a vector,
| with indices instead of pointers.
| nwatson wrote:
| There are pauseless GCs for Java, e.g. Azul Systems
| https://www.azul.com/products/components/pgc/ , seems like a
| winning approach.
| vips7L wrote:
| ZGC also has sub millisecond pauses
| panick21_ wrote:
| Yes. And if you read their papers, when they were able to add
| explicit hardware support they were able to do even better.
|
| I really think had vertical companies from the 90s not all
| died, seriously developing chips for personal computers
| (phones,laptops) with hardware support for the most important
| GC features would have been a huge winner.
|
| Apple could have done this but they were already bound to
| Object-C because of Next.
|
| Sun had the amazing Self VM in the early 90s already. And they
| made their own chips as well. But unfortunately they went with
| Java and instead of making a chip to support GC they tried to
| make a chip that implemented Java.
| pjmlp wrote:
| The amazing Self VM is the genesis of Hotspot anyway.
|
| They didn't try, they actually did them, and ARM did that as
| well.
| panick21_ wrote:
| Yeah Sun of course later bought the tech they developed
| themselves. Totally dumb.
|
| What do you mean 'they did them' as far as I know they
| never did extensions to their main CPU specifically to
| improve garbage collection.
| pjmlp wrote:
| The story between Objective-C, Strongtalk, SELF, Tcl and
| Java is a bit more complicated than that.
|
| No they didn't, but the point was about Java specific
| CPUs,
|
| https://en.m.wikipedia.org/wiki/PicoJava
|
| And then there was
|
| https://www.electronicdesign.com/technologies/digital-
| ics/ar...
| panick21_ wrote:
| I know its more complicated, I was not trying to give a
| full history.
|
| As I pointed out in my original comment:
|
| > But unfortunately they went with Java and instead of
| making a chip to support GC they tried to make a chip
| that implemented Java.
|
| I think in the 2000s Azul hit on how to combine high
| performance JIT, high performance GC with a number GC
| specific updates for the hardware and the OS.
| ordu wrote:
| Sadly they didn't discussed the main selling point of GCs (in my
| opinion). GC can move objects in memory and often does it. It can
| affect cache locality, so less of cache misses. Moreover no more
| heap fragmentation. There are different strategies to do that, so
| theoretically one can choose the best one after the code was
| profiled. Though practically it may not be an option, because the
| chosen language gives you a fixed unchangeable implementation of
| a GC.
|
| I bet that these benefits of GC can be great in some tasks,
| though I know no real world examples of software explicitly
| relying on it. Some servers on Java using terabytes of RAM, I
| think? But no one talks about it. My curiosity have nothing to
| consume, and it is hungry.
| hayley-patton wrote:
| There are moving collectors which use profiling information to
| try to improve locality. It appears they produce a 15-30%
| speedup or so.
|
| https://safari.ethz.ch/architecture/fall2017/lib/exe/fetch.p...
|
| https://www.microsoft.com/en-us/research/wp-content/uploads/...
| jandrewrogers wrote:
| Database engine implementations sometimes work similar to this,
| being locality optimizing machines. Objects in memory can be
| moved and reorganized dynamically. Ensuring that references to
| those objects remain valid is not inexpensive because it
| requires indirection to dynamically resolve object locations
| and may require patching state referencing the object.
|
| The performance impact is only tolerable for database engines
| because the context and scope is sufficiently constrained such
| that clever software design elements can ensure that the cost
| is applied lazily and expensive effects are avoided except in
| rare cases. Even in database engines, thrashing your CPU cache
| is a common side effect of not getting the design right. I
| can't imagine trying to make this perform well for general
| purpose software.
| verdagon wrote:
| Take a look at Mesh [0] which brings compaction to a malloc-
| like system. I could see an RC-based or single-ownership-based
| language using it to avoid fragmentation.
|
| [0] https://tiba-jrchang.medium.com/mesh-compacting-memory-
| manag...
| twoodfin wrote:
| What it means to be a "systems" programming language is a matter
| of some debate, but there is at least one definition that's only
| coherent in a resource management model isomorphic to RAII/ARC.
| melissalobos wrote:
| This sounds very nice, but just to make sure I understand your
| meaning: If you have a properly designed "systems" programming
| language(even if manually managed) you would release resources
| exactly(or nearly so) at the time when a proper ARC/RAII
| implementation would. Is that correct for your meaning? If so I
| fully agree.
| ncmncm wrote:
| At this late date, a language inadequate to express ARC/RAII
| is better described as a toy. There is nothing wrong with
| toys, but they are not to be confused with professional
| tools. A systems language is, first and foremost, a
| professional tool.
| jandrewrogers wrote:
| It is more subtle than this. Systems programming sometimes
| involves a resource life cycle that has semantics that map
| neither to a GC nor RAII/ARC. This is because systems
| programming often involves the direct management of hardware
| resources, like storage.
|
| For example, while resource acquisition and release may be
| deterministic, there is no implication that a released
| resource may be immediately reused or reacquired. This is not
| pattern most software engineers are used to. The reason for
| this is that _hardware_ may hold a lingering reference to a
| software resource even if the software does not -- and
| hardware doesn 't use reference counters or destructors. An
| arbitrary amount of time may pass between a resource being
| released in software and that same resource being available
| for reuse again in software. Many people new to systems
| engineering make the mistake of reusing a resource that was
| released but not reusable.
|
| Systems programming languages don't just ensure deterministic
| release of resources, they also need to provide a way to
| express that the hardware may hold references to objects the
| software has fully released such that it isn't really
| released. This is not something the average programmer has to
| deal with, but this is part of what makes systems engineering
| different.
| twoodfin wrote:
| My point was that if by "systems" programming, you mean
| prioritizing efficient use of limited system resources (CPU
| cycles, bytes of memory, ... ) then there's no superior
| alternative to the precise control & reliability afforded by
| ARC/RAII.
| moonchild wrote:
| Chris makes some points which are interesting in their own right,
| particularly the one comparing the amount of research which has
| gone into tracing/copying gc vs rc. But I think there is an
| unmentioned bias which is worth pointing out. Tracing gc has
| found massive success in enterprise java applications, which:
|
| - Need to scale in terms of code size and contributor count, and
| therefore need the modularity afforded by tracing gc
|
| - Need to scale in terms of heap size, and are therefore more
| likely to run into the pathological fragmentation issues that
| come from non-compacting gc
|
| - Need the higher throughput afforded by tracing gc
|
| - Generally run on massive servers that have the cores, memory
| size, memory speed, power budget, and heat dissipation required
| to support high-quality tracing gc
|
| Contrariwise, swift is a product of apple, a laptop and phone
| company, and is used to make laptop and phone apps which:
|
| - Are unlikely to ever get really huge (people balk at large
| downloads)
|
| - May have limited memory, and need to share it with other apps
|
| - Run on battery-powered hardware with little or no active
| cooling, and so need to attempt to conserve power
|
| No silver bullet, as they say. I implement APL, which absolutely
| cannot do without reference counting, except perhaps in some
| novel configurations. I think that for the more usual case of a
| pointer-chasing language, tracing gc is almost certainly the
| right choice.
|
| Lastly, however, I would be remiss not to bring up bacon et al,
| 'A Unified Theory of Garbage Collection'[0], which notes that
| tracing gc and reference counting are not really opposed to one
| another, and that a sufficiently advanced implementation of
| either will approach the other. Lattner mentions barriers (though
| as a sibling mentions, barriers are an order of magnitude cheaper
| than reference count twiddling); on the other side of the coin,
| consider deferred reference counting, one-bit reference counts,
| ...
|
| The bottom line being that memory management is _hard_, in all
| cases; a sufficiently robust and general solution will end up
| being rather complicated no matter what it does; and, again,
| there is no silver bullet.
|
| 0.
| https://courses.cs.washington.edu/courses/cse590p/05au/p50-b...
| saagarjha wrote:
| > Are unlikely to ever get really huge (people balk at large
| downloads)
|
| Wait until you see how big apps are.
| klabb3 wrote:
| > I am totally convinced that ARC is the right way to go upfront.
| [...] It gives you deterministic behavior[...]
|
| Does it? In a meaningful way? For arc with strong recount>1,
| which constitutes the main use case in my experience (weaks are
| unenrgonomic, for one), which means that the deterministic
| destruction is not meaningful locally, as opposed to the modern
| manual alternative, RAII, for instance. Am I missing something?
| verdagon wrote:
| I'm not sure most people use this technique, but when I was
| using RC-based systems, I would often assert that an object's
| refcount was 1 just before I let go of that last reference.
| Suddenly, we have RAII-like predictable destruction.
|
| I even built it into Vale as one of its memory management
| options called "constraint references" [0] though later
| switched to generational references which gave us RAII without
| the halts. I sometimes wonder how far I could have taken that
| RC + assert model.
|
| In my experience, the vast majority of objects even in an RC'd
| language do have strong refcount = 1. Perhaps we were in
| different domains though, I was mostly in game dev and app dev.
|
| [0] https://verdagon.dev/blog/raii-next-steps
| kgeist wrote:
| I don't think it's quite deterministic because a simple
| variable going out of a scope can trigger deallocation of a
| random object tree (which can be large), which you cannot
| always predict locally (as a programmer) and which can affect
| performance on a critical path or introduce side effects at
| unpredictable times, plus the overall possibility of having
| uncollected cycles - so in the end for a programmer -- who's
| not willing to manually trace ownerships themselves to
| understand the behavior in every detail -- it has quite same
| disadvantages as a tracing GC (deallocation may kick in at
| "unpredictable" times) with the additional disadvantages such
| as: fragmentation and poorer memory locality in the long term
| (no compaction), possible memory leaks (due to object islands),
| temporary young generation objects being as expensive as old
| generation objects (no bump pointer allocation in a nursery),
| excessive RC increments/decrements can trash performance (and
| if it's atomic it can IIRC force cache invalidation, which also
| makes the behavior less deterministic).
|
| So I don't think it's a silver bullet, it's just a different
| kind of GC with its own set of disadvantages. I think most of
| the time RC is chosen because it's simple to implement or
| because there's a legacy system where RC is the only choice,
| and all the other "benefits" are just afterthoughts to further
| justify the choice.
| gmueckl wrote:
| Determinism in ARC vs. GC discussions is probably more of a
| reproducability thing. With ARC, a program will always free
| memory in the same spots in the same way whem running on the
| same input. In most GCs, this is not the case. Their
| behaviordepends very much on external factors like passed
| wall clock time or system-wide memory pressure (e.g. if it
| rns collection whem requesting more memory from the OS
| failed).
|
| This kind of GC behavior can make bugs relatedcto external
| resources much harder to find. And benchmarking an allocation
| heavy program with a nondeterministic GC can be hellish due
| to the increased distribution of measured runtimes.
| kgeist wrote:
| >With ARC, a program will always free memory in the same
| spots in the same way whem running on the same input
|
| It's true for pure single-threaded functions but I doubt
| it's 100% the case for asynchronous code which depends on
| side effects (user input, system events, async I/O): a
| slightly different timing and your reference count is
| different from the previous run, and as a consequence your
| object trees are deallocated differently, and in different
| configurations, each time as well. But it's only a problem
| if objects are shared between threads, though.
|
| Even with single-threaded code, a random change to your
| codebase which increments a reference here and there can
| invalidate all your prior assumptions and have a ripple
| effect on the entire system.
| magicalhippo wrote:
| > Even with single-threaded code, a random change to your
| codebase which increments a reference here and there can
| invalidate all your prior assumptions and have a ripple
| effect on the entire system.
|
| This is true. The standard library for the language we
| use at work (Delphi) had a bug in its thread pool class
| which was of this nature. The threads in the pool would
| wait for work to be available, pop a work item, do work
| and wait for more work again.
|
| The issue was that the reference count of the previous
| work item would not decrement until the local variable
| holding the work item was overwritten by the new work
| item (increasing the reference of the new work item
| also). In particular, if there was no more work, this
| reference would be held until program termination.
|
| This caught me, as a library user, by surprise as I
| expected the work item to go out of scope and be
| destroyed when it had been executed, since no more
| references should be held at that point.
|
| That said, spotting these bugs are quite easy with ARC
| once you dig into the code, given that the points where
| the references can be increased or decreased are
| deterministic. So as long as you have access to the
| source code of dependencies it's fairly easy to find the
| reference counting points.
|
| GC on the other hand is completely async and opaque for
| the most part.
| mirekrusin wrote:
| Arc is deterministic, you get the same results on multiple
| runs (minus not synchronized async fun of course), you can
| profile it etc. - you don't have this luxury with gc.
| pjmlp wrote:
| Not if the run depends on external data, which might happen
| to create a data forest with stop the world effect caused
| by domino effect of deletions.
| Decabytes wrote:
| I've never done this personally but I know in some of the
| languages I've used you can turn off garbage collection. If you
| really cared about the latency, then you couldn't't you just
| manually collect at certain points in the program?
|
| Or you could use a language like D which lets you write a program
| without the garbage collector when needed. You definitely have
| less tools when you do that, but if you are in a situation where
| you know you want to manage memory manually you probably will
| evaluate that trade off.
| torginus wrote:
| I think Chris makes an excellent point that is the mortal flaw of
| GC-s, anyone who argues about overhead or pauses or throughput is
| getting it wrong - the problem is determinism, and interaction
| with native objects.
|
| You cannot reason about when the computer can/will get rid of
| native objects that are referenced by the GC, and doing so raises
| a billion of hairy questions, many of them discussed at length in
| the article.
|
| Why is this important?
|
| This is implies the model of SPA frameworks, which rely on this
| exact thing, fundamentally cannot be made to work well, since
| they rely on referencing native objects from JS.
|
| This invalidates the entire way we build GUIs/websites nowadays.
| I'd say that's a pretty darn important issue.
| jFriedensreich wrote:
| Could you elaborate how this is connected to js and SPA
| frameworks? I totally get how reasoning and determinism is
| critical for sound processing, embedded low level, operating
| systems etc. But why would this be relevant for normal
| application or web development?
| torginus wrote:
| SPA frameworks manipulate the DOM through JS, which means
| they need to take a reference to DOM elements.
|
| In a static website, once you navigate away from a page, the
| browser can deallocate all the DOM resources related to the
| old page, which can be quite heavy (images etc.).
|
| In an SPA-driven website, the DOM is created by JS. Once you
| 'navigate' away (you tell the framework to render a different
| set of DOM components), the old ones still linger around in
| memory until the next GC cycle, meaning the browser has to
| wait for the GC to run before it can free them.
|
| Even worse, GC's tend to be tuned for GC memory pressure, and
| if you have a ton of GC objects holding onto native
| resources, the GC has no way of telling it needs to run,
| because from its perspective, the GC heap is small and it has
| no idea about the native objects.
| jFriedensreich wrote:
| Still dont get it. Obviously a dom implementation that is
| tied to a garbage collected script language has to be tied
| to the garbage collection in some way to free connected
| resources, this fact only shows that it is not a trivial
| task which is obvious. i still do not see how this is an
| argument why a garbage collected language is a bad idea for
| that usecase.
| draw_down wrote:
| armchairhacker wrote:
| Personally I find ARC really easy to reason about and worth it.
| Essentially, a strong reference goes to "child" objects, a weak
| reference goes to "parents" or "siblings".
|
| You can think of your app's data structures as a forest. The tree
| roots are your application and any globals, and the children are
| their fields, which have more fields, and so on. Sometimes a node
| stores a reference to something which is not its child/nested
| field but another node's: e.g. a view might have a reference to
| its controller (its parent) another view which is not a subview
| (its sibling), or even a subview of a subview (descendant). The
| point is, this other node has a different parent, it already has
| a strong reference, so inside the child / sibling it's a weak
| reference.
|
| Another perspective if your familiar with functional programming,
| especially if you've worked in a language like Haskell that
| "doesn't have" references: weak references are conveniences of
| the object-oriented paradigm which, in the functional paradigm,
| you would just pass around as function arguments instead. It can
| be really annoying to update an object without mutation when the
| object is referenced by other objects - in mutating, object-
| oriented code, those would be weak references.
| fauigerzigerk wrote:
| _> Personally I find ARC really easy to reason about and worth
| it_
|
| How do you reason about a closure that captures some stuff and
| gets passed to a library function? @escaping doesn't give you
| enough information to reason about all lifetimes that could
| possibly matter.
| cmrdporcupine wrote:
| I find this whole conversation frustrating or somewhat confusing
| because it's a weird distinction: Reference counting _is_ garbage
| collecting. It 's not a mark & sweep or generational tracing
| collector, but it's a collector. And in fact it even traces,
| though the tracing is explicit (in the form of the declaration or
| management of references.)
|
| My copy of the Jones/Lins Garbage Collection book I'm looking at
| on my shelf makes this quite explicit, the chapter on reference
| counting makes it clear it's one form of garbage collection.
|
| When I was messing with this stuff 20 years ago, that was my
| understanding of it, anyways.
|
| There are advantages and disadvantages to reference counting. I
| think some of the confusion in terminology and some of the
| frustrations (or opposite) here comes from the rather awkward way
| that Objective-C implements reference counting as something you
| have to be explicitly aware of and careful about, even with ARC.
| My brief stint doing iOS dev made this clear to me.
|
| But I've used (and written) programming languages that had
| transparent automatic garbage collection via reference counting,
| and the programmer need not be aware of it at all, esp if you
| have cycle detection & collection (or alternatively, no cycles at
| all). And, there are compelling RC implementation that are
| capable of collecting cycles just fine (albeit through a limited
| local trace.)
|
| Anyways, he's right that RC can be better for things like
| database handles etc. Back in the day I/we used reference
| counting for persistent MUDs/MOOs, where the reference counts
| were tracked to disk and back, 'twas nice and slick.
| pizlonator wrote:
| ARC makes ref counting a part of semantics in the sense that
| the deref that reaches zero immediately runs the destructor,
| and destructors can have any effects they like.
|
| Therefore, it's not really valid to say that ARC "is" garbage
| collection.
|
| Reference counting is only a form of garbage collection if it
| is totally transparent to the programmer, and it's not like
| that in ARC.
| pjmlp wrote:
| Which is why when Apple introduced new ARC optimizations last
| year, they had a talk about possible gotchas that might crash
| the application due to them.
| SamReidHughes wrote:
| You might as well classify allocating without freeing as a form
| of garbage collection. If the implementation can fail to
| collect cycles and the language can create them, thus leaking
| memory, lumping in reference counting with real garbage
| collection is a bad definition because it smears together a
| crucial distinction.
|
| I get that some people, or many people, or some book, have
| defined it that way, but others have used it in the meaning as
| distinguished from plain refcounting. Many developers have
| always understood GC and refcounting (in languages with
| mutation) to be mutually exclusive concepts. That definition is
| used in practice, and when people say, "I'm going to implement
| garbage collection for my programming language," what they mean
| is one which doesn't leak memory.
|
| A reason people use this other definition may be that plain
| reference counting and "having a garbage collector" are
| mutually disjoint, even if you define "garbage collection" as
| including reference-counted implementations. Making garbage
| collection a distinct concept from the set of acts a garbage
| collector might do will always be fighting against how language
| usually works.
| kibwen wrote:
| They get lumped together because when some people say
| "garbage collection" they mean "whatever Java specifically
| does", and when other people say "garbage collection" they
| mean "automatic dynamic lifetime determination" (as a
| contrast to C-style "manual static lifetime determination",
| or Rust-style "automatic static lifetime determination").
| Kranar wrote:
| >You might as well classify allocating without freeing as a
| form of garbage collection.
|
| Of course, Java even explicitly supports this garbage
| collection strategy:
|
| https://openjdk.java.net/jeps/318
|
| The best definition I've read comes from Raymond Chen, which
| is that garbage collection is a system to simulate an
| infinite amount of memory:
|
| https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13.
| ..
|
| This definition is very elegant in that it allows one to
| reason about program correctness across all garbage
| collectors without having to know any of the implementation
| details, be it reference counting or the many flavors of mark
| and sweep. Basically garbage collection is not an
| implementation detail, nor is it a specific algorithm or even
| class of algorithms, garbage collection is a goal, namely the
| goal of achieving an infinite amount of memory. Different
| algorithms will reach that goal to different degrees and with
| different trade-offs.
| cmrdporcupine wrote:
| RC need not leak memory. If it's done right, it doesn't.
|
| That Objective-C exposed the innards of the system badly
| enough that it was possible to leak, that's not intrinsic to
| RC. It's just yet another piece of the awkward legacy puzzle
| that is Objective-C.
|
| As for cycles.. Cycle detection in RC has been a solved
| problem for over two decades. I implemented it once, based on
| a paper from some IBM researchers, if I recall.
| tomp wrote:
| Can you link to some resources/papers detailing how to
| solve this problem? I'm only aware of "RC+GC" solution
| (i.e. adding a full tracing GC in addition to RC to find
| and collect cycles).
| hayley-patton wrote:
| David F. Bacon and V. T. Rajan, Concurrent Cycle
| Collection in Reference Counted Systems
|
| https://www.cs.purdue.edu/homes/hosking/690M/Bacon01Concu
| rre...
| cmrdporcupine wrote:
| That's the one I was thinking of, and I once implemented
| that one myself in C++ back in, uh, 2001, 2002, during a
| bout of unemployment in the .com crash.
|
| I guess I just assumed that there's been some advancement
| in the world since then.
| mojuba wrote:
| Cycles in RC is a fundamentally unsolvable problem because
| there are situations when you in fact want an unreferenced
| cycle exist in memory at least for a while.
|
| Imagine a slow async operation such as a network request,
| where a GUI object is locked in a cycle with a self
| reference in a callback. Before the network response
| arrives the GUI object may be removed from the view
| hierarchy (e.g. the user has navigated away), however,
| whether you want the cycle to be broken or not depends on
| your needs. You may want to wait for the results, update
| stuff and break the cycle manually, or you may choose to
| completely discard the results by declaring weak self.
|
| Therefore, solving the problem of cycles with RC I think
| means giving more manual control. Explicit weak pointers
| solve part of the problem, but the "I know what I'm doing"
| scenario requires great care and is obviously error prone.
| To my knowledge this hasn't been solved in any more or less
| safe manner.
| mirekrusin wrote:
| How come python works then? It uses rc with cycle
| detector, doesn't it?
| pjmlp wrote:
| Yes it does.
| SamReidHughes wrote:
| I think in the scenario you describe, you always want to
| hang onto a reference to the unfinished network activity
| because you will need to be able to interrupt it and
| clean it up. You never(?) want to leak active running
| code in the background, especially not code with side
| effects, which isn't described by some kind of handle
| somewhere, in part because there's probably a bug and in
| part because people can't understand how your code works
| if there isn't a handle to the task stored in a field
| somewhere, even if you don't use it.
| mojuba wrote:
| Of course there's a reference to the network resource and
| that's the reference that creates a cycle via its
| callback defined in a bigger object (e.g. GIU view) that
| awaits the results. There may be reasons why you want to
| keep the cross-referencing pair of objects alive until
| the request completes. Not saying there aren't
| alternative approaches, but it may be easier to do that
| way.
|
| I came across this while coding in Java, when I realized
| a network request may be cancelled prematurely just
| because the GC thinks you don't need the network resource
| anymore, whereas in fact you do. There's no easy solution
| to this other than reorganizing your whole request cycle
| and object ownership. Classical abstraction leak.
| SamReidHughes wrote:
| I mean, the GUI object with its reference to the network
| request should never be detached like that. The network
| request handle should be moved to something else that
| takes care of it, if the GUI object can't, and it should
| not be calling a callback tied in a strong way to the GUI
| object's existence. Granted, I wasn't there when you had
| this, and I'm not saying there's an easy solution. What I
| am basically saying is there should be a chain of
| ownership to some local variable on the stack somewhere,
| instead of a detached GUI view. I'd view the resource
| leak you describe as hacky, and a class of design
| decision that has caused me a lot of pain in gui
| software. Well, maybe I had to be there.
| mojuba wrote:
| Yes, you can move your references around to find a
| suitable place, but how does that solve the problem of
| lifetime control? How do you guarantee a network resource
| is alive for as long as you need it under GC, and under
| ARC?
| SamReidHughes wrote:
| By holding a reference to it that is grounded to a GC
| root i.e. an an ownership chain to a local variable that
| will dispose of it. Also which can be used to interrupt
| the background task. So the network connection, callback,
| etc., are not in a detached cycle.
|
| With the design you're describing -- without the ability
| to interrupt the background task -- I would bet you tend
| to get race conditions of one kind or another.
| prionassembly wrote:
| So many words, so little clarity.
| solarengineer wrote:
| Are you referring to the response by SamReidHughes? I
| understood it perfectly, and indeed, that was my own
| understanding as well.
|
| Could you elaborate on what you feel should be explained
| with more clarity?
| zarzavat wrote:
| In the modern sense of the word, a GC is an algorithm that
| manages memory automatically and _without cycles_. A GC must
| maintain the property that a leak is only possible if an object
| is reachable from a root. RC on its own does not obey this
| property.
|
| Reference counting is not GC in the modern sense unless it also
| includes a cycle collector. So Swift is not GC'd (RC only) yet
| Python _is_ GC'd (RC+CC).
| knome wrote:
| >RC on its own does not obey this property.
|
| It is sufficient in a functional language that lacks
| haskell's lazy capacity to "tie the knot". Erlang's data
| structures, for example, are fully immutable, and it is not
| possible to create a cyclic data structure. Since there
| cannot be a cyclic data structure, reference counting would
| be sufficient to manage its memory automatically.
|
| It would still suck since it has to constantly write to all
| objects being referenced, but it would work.
| cmrdporcupine wrote:
| There are RC systems where the counts are held external to
| the objects, which can be a bit more cache friendly.
| Kranar wrote:
| You want the opposite behavior in order to be cache
| friendly, an RC system where the counts are stored next
| to the object.
| cmrdporcupine wrote:
| Not necessarily. The issue is that releasing a reference
| at the top of a tree of objects has the potential to walk
| a large graph of objects (doing downcounts at each) that
| are not in cache, causing some thrash.
|
| In an ideal world and in a program running with a not-
| huge # of objects you could keep the entire basket of
| reference counts in L1 cache, separate from the objects.
| Then decrements would be potentially cheap. A bit utopian
| though.
| Kranar wrote:
| > A GC must maintain the property that a leak is only
| possible if an object is reachable from a root. RC on its own
| does not obey this property.
|
| This is certainly a false claim then. What you're describing
| is a precise garbage collector, but there also exist
| conservative garbage collectors which will fail to reclaim
| objects even when no cycle exists so long as any integer
| interpretation of currently allocated memory shares the same
| integer interpretation of an object's memory address.
|
| GNU Java, and the Mono implementation of .NET both use the
| Boehm conservative garbage collector and they are most
| certainly still considered garbage collected systems:
|
| https://en.wikipedia.org/wiki/Boehm_garbage_collector
| zarzavat wrote:
| This isn't formal science. We are practitioners and so
| definitions have to be practical. If Boehm GCs really leak
| as much as you claim then nobody would use them.
|
| The issue with cycles is that it can easily lead to
| significant memory leaks if a large subgraph gets caught in
| a cycle. A definition of GC that excludes this capability
| does not have the contract of "You only have to worry about
| reachability from roots".
| Kranar wrote:
| >This isn't formal science.
|
| Computer science is absolutely a formal science in every
| sense of the word.
|
| >If Boehm GCs really leak as much as you claim then
| nobody would use them.
|
| I mean if anything this argument applies more to your
| characterization that reference counting easily leads to
| memory exhaustion due to cycles than it does to
| conservative garbage collectors. If RC based garbage
| collectors leaked as much as your post implies then
| nobody would use them. And yet Swift, a language that is
| almost exclusively used with large graphs(that's how most
| UIs are represented in memory) and that exhibits all
| kinds of non-deterministic shared ownership uses RC based
| garbage collection. Objects involved in UI code are
| shared in wildly unpredictable ways with no clear
| hierarchy among layouts, event handlers, observables, IO
| systems, etc...
|
| >A definition of GC that excludes this capability does
| not have the contract of "You only have to worry about
| reachability from roots".
|
| Exactly, because that's not part of the contract for a
| GC. That's part of the contract for a tracing garbage
| collector:
|
| https://en.wikipedia.org/wiki/Tracing_garbage_collection
|
| When your definitions don't fit reality then it's time to
| adjust your definitions, not reality.
| josephg wrote:
| > Reference counting is garbage collecting.
|
| I hear you, but this is just a terminology thing.
|
| I was taught that the "garbage collector" term _only_ refers to
| tracing garbage collectors. It sounds weird and wrong for me to
| hear the term GC referring to to things like C /Zig's manual
| memory management (malloc and free) or Rust's borrowck. And it
| sounds like its the same for you, in reverse.
|
| In my school, the whole class of systems are "memory management
| systems" and "Garbage Collector" is one such class of approach.
| (What you would call tracing GCs).
|
| But I don't think either of us is canonically right. Its just a
| "pop" vs "soda" thing. You and Jones/Lins say "pop". Me and
| Chris Lattner say "soda". Its no big deal.
| melissalobos wrote:
| > Reference counting is garbage collecting
|
| I am drawing a blank on google scholar at the moment, but I
| recall a very popular paper on exactly this topic of RC as the
| other side of Tracing GC.
| hayley-patton wrote:
| "A unified theory of garbage collection"?
|
| https://dl.acm.org/doi/10.1145/1028976.1028982
| melissalobos wrote:
| Yes! Thank you, I couldn't think of the terms to search for
| to find this.
| cmrdporcupine wrote:
| Nice, will put this paper in my bookmarks for later read.
|
| I feel like once you stop and think about it, the
| distinction between a 'tracing' GC and a 'non-tracing' GC
| is specious: To do its work at acquire and release, RC
| collector must in fact trace a tree of objects. It's just
| that the semantics of how this is declared might differ,
| or, more specifically the time(s) in which it does this
| work.
| hayley-patton wrote:
| Some people refer to only tracing algorithms (mark-sweep,
| copying, etc) as "garbage collection". The best reason I can
| think of is marketing.
|
| There are generational reference counting systems e.g. ulterior
| reference counting [1] and age-oriented collection [2].
| "Tracing" usually refers to scanning a heap to work out
| liveness all at once, which reference counting doesn't do;
| though deferred reference counting scans the roots to collect,
| and ulterior reference counting traces a nursery. Deferring
| these (more common) updates to reference counts improves
| throughput considerably.
|
| [1]
| https://people.cs.umass.edu/~emery/classes/cmpsci691s-fall20...
| [2] https://users.cecs.anu.edu.au/~steveb/pubs/papers/aogc-
| cc-20...
| jollybean wrote:
| Perhaps the most interesting thing is that it's all moot for the
| vast majority of applications. If it's done reasonably well, it
| just won't matter. 'Most' of the time. And for the times it does
| well, you'd probably be better of finicking away manually with
| things.
| draw_down wrote:
| randyrand wrote:
| A lot of programs have to deal with JNI or weak references.
| jurschreuder wrote:
| "I'm now a coding monkey but I'm close to becoming a manager".
|
| I really wish managers and aspiring managers would be snapped out
| of existence. They should be sent to the mines. If a developer
| does a job interview and they say they actually WANT to become a
| manager some day, that's the biggest red flag currently known to
| me. I'd rather hire a drug addict than an aspiring manager.
| mirekrusin wrote:
| Exactly, it's like athlete wanting to be a referee.
| munchler wrote:
| More like an athlete wanting to become a coach, which is very
| common.
| invalidname wrote:
| So this is a lot of gibberish but let me sum up to you the RC vs.
| GC:
|
| GC advantages:
|
| * Scales better when done right - cheap allocations simplified
| code etc.
|
| * Can be heavily concurrent and use multicore
|
| RC advantages:
|
| * Predictable performance
|
| * Uses less RAM
|
| For GUI RCs smaller memory footprint and predictable performance
| are great. Hence swift uses ARC which is also faster than most RC
| implementations (which typically have a lot of overhead).
|
| For servers where overall performance matters more than 100%
| smooth consistency and RAM is cheaper... GCs have some
| advantages. A GC can run on idle and clean a huge amount of
| garbage without a problem. It can do so concurrently and leverage
| the multicore nature of modern CPUs.
|
| This is one of those, right tool for the right case.
| jandrewrogers wrote:
| > For servers where overall performance matters more than 100%
| smooth consistency and RAM is cheaper... GCs have some
| advantages.
|
| I am having a difficult time parsing this in a way that makes
| sense.
|
| Generally speaking, overall performance on servers is a _direct
| product_ of being able to take strict control of when things
| happen and minimizing RAM footprint. That "smooth consistency"
| is how performance happens because there are important classes
| of software optimization that don't work if this is not a
| property of your runtime. Similarly, high-performance server
| architectures are usually not heavily concurrent even on
| massively multicore systems for good reasons that a GC doesn't
| address.
|
| The only advantage a GC has in this context that I can think of
| is that it might reduce some types of bugs in some types of
| code.
| invalidname wrote:
| I come from the world of JVM so here we do have multicore
| servers and you might see a peek where a particular request
| takes longer but overall the performance will be amazing.
| This isn't OK for GUI but is great if you have a HUGE server
| that can chug through tons of data with a huge amount of
| RAM/cache.
| panick21_ wrote:
| But this problem has technically mostly been fixed by Azul
| and is making its way into open as well, we have ZGC and so
| on now. Pauses are rather short even with large heaps.
| jandrewrogers wrote:
| I used to write a lot of performance-optimized server code
| in Java. Even with heroic effort, it is difficult to make
| the JVM perform even half as fast as not-that-optimized C
| or C++ -- other languages I wrote the same kinds of code
| in. And highly optimized C++ is many times higher
| throughput than the JVM equivalent on the same hardware,
| pretty much always. The reasons why are well-understood
| technically.
|
| This cannot be blamed entirely on the GC. The JVM makes
| many other choices which make optimization difficult, and
| its original theories about what is important for
| optimization haven't stood up over time. It was not
| designed to be performant, just good enough for most
| applications that are not that performance sensitive.
| Programming languages often have many objectives and
| priorities aside performance. I use Python quite a lot and
| it is usually much slower than the JVM.
| invalidname wrote:
| When you say "used" how long ago are you talking about
| and for what types of loads?
|
| I saw Java rewrites running circles around C++ code it
| replaced. It's a matter of tuning and picking the right
| configurations/tasks. Some things Java does really well,
| others it sucks at. If you need near memory access and
| have constant loops over arrays of objects then Java will
| be slower than that when compared to C++ because of the
| memory layout (Valhalla will improve that).
|
| Network throughput is another weak point which amazingly
| NodeJS does better. Loom resolves that problem.
|
| There's also a lot of choices when it comes to GCs
| nowadays. Java used to be a dog on large memory heaps.
| This is no longer the case with Z, G1 etc. but it's very
| possible you hit a problematic spot.
|
| One of the nice things about GC performance is that you
| can connect a monitoring tool to a production server and
| optimize memory performance by viewing actual usage.
| Monitoring and observability in Java is at another level
| entirely.
| anonymoushn wrote:
| > If you need near memory access and have constant loops
| over arrays of objects then Java will be slower than that
| when compared to C++ because of the memory layout
| (Valhalla will improve that).
|
| In a former life we used Scala magic to pretend we had an
| array of structs. Otherwise, yeah, this is a huge issue
| facing JVM apps.
| invalidname wrote:
| Valhalla will solve this. I disagree that this is a huge
| issue. There are some edge cases where this is
| significant but for most web/enterprise applications I
| don't think this is huge.
| KerrAvon wrote:
| Don't know why you're getting downvoted, you are completely
| correct.
|
| I will add that optimizing for memory usage is hugely important
| to system performance on Apple's devices.
|
| I do wonder about the idea that it isn't universally important;
| all computers are RAM/cache constrained except maybe HPC
| equipment.
| erik_seaberg wrote:
| Computers are constrained in bus bandwidth as well, and it's
| harder to add more. RC tends to require strongly consistent
| counter bumps on the bus even for objects whose exact
| lifetimes you might not care about so much. The costs of GC
| are amortized over more work, though the pauses can hurt.
| fay59 wrote:
| One data point from the Prodfiler folks was that even if the GC
| cost to throughput is relatively low, some programs will still
| spend up to 25% of their time doing garbage collection. This will
| show up on your cloud bill even if it doesn't show up to that
| extent in your performance analysis.
| Someone wrote:
| Some programs spend over 50% of their time doing reference
| counting (http://iacoma.cs.uiuc.edu/iacoma-papers/pact18.pdf),
| and that will show up I'm your throughput measurements.
|
| I expect you can find papers giving numbers in the same
| ballpark for malloc/free, too. A MVP of JavaScript, for
| example, that boxes everything, combined with a benchmark that
| creates a d destroys lots of objects.
| pjmlp wrote:
| And yet languages with tracing GC outperform Swift.
|
| https://github.com/ixy-languages/ixy-languages
|
| Objective-C GC failed to work reliably due to everything C allows
| to do, and people mixing frameworks compiler without being
| enabled.
|
| Automating Cocoa's retain/release calls was more reliable, and
| makes much more sense.
|
| Swift naturally needed to follow the same path to easily
| interoperate with the Objective-C runtime.
|
| A tracing GC would mean having something like .NET COM Callable
| Wrapper and Runtime Callable Wrappers, for interoperability with
| COM, which is a much greater engineering effort.
|
| So naturally reference counting as GC algorithm for Swift makes
| sense.
|
| Everything else is just cargo cult.
| astrange wrote:
| This benchmark is not good enough to make a decision about how
| you should build an entire OS.
|
| They did make this decision when designing ARC and Swift, and
| they did have data for it, and first-order effects of how fast
| you can make a single task can't be the determining factor
| there. In particular you need to know how disruptive running
| the task will be to other more important stuff.
| pjmlp wrote:
| You can benchmark it against Android or Windows Phone devices
| then.
|
| Naturally with similar hardware across the board.
| astrange wrote:
| You can do that by looking up which one ships with less RAM
| installed. I believe the common wisdom is still that it's
| not Android.
| pjmlp wrote:
| To save you the trouble, any Windows Phone 7 device,
| using XNA or Silverlight as OS APIs, 512 MB.
| GeekyBear wrote:
| The nice thing about designing your own hardware stack:
|
| >fun fact: retaining and releasing an NSObject takes ~30
| nanoseconds on current gen Intel, and ~6.5 nanoseconds on an M1
|
| https://twitter.com/Catfish_Man/status/1326238434235568128
| astrange wrote:
| This has nothing to do with custom hardware, it's one of the
| few differences that's actually due to ARM vs x86. Though, part
| of it is from being a unified memory SoC.
| Tagbert wrote:
| I heard in an interview with Craig Federighi, that in
| designing the Apple Silicon chips they had looked at specific
| operations that were frequently used by their OS and software
| and optimized those on their chips. Reference counting was
| one of those operations and that was why the speed of that
| operation was so much faster on Apple Silicon. Sure those
| other things about unified memory contribute speed to all
| kinds of operations, but they really focused on some of them
| to let their hardware and software work well together.
| pjmlp wrote:
| Now try Swift outside that hardware stack.
| GeekyBear wrote:
| Swift has been used to write parts of MacOS that are used on
| Intel today.
|
| The good faith efforts to write parts of a consumer OS in a
| garbage collected language that I can remember are
| Microsoft's effort to write Longhorn in C# and Google's
| effort to write part of Fuchsia in Go.
|
| Both efforts failed for performance reasons.
| vips7L wrote:
| Go has a bad GC. Go relies on the compiler to do escape
| analysis and allocate on the stack. Once you actually start
| heap allocating you hit massive pauses. It's why Go does so
| bad on the binary tree benchmark.
| jatone wrote:
| > Once you actually start heap allocating you hit massive
| pauses.
|
| citation needed. go afaik has millisecond pauses at most.
| maybe you're thinking of throughput losses due to forcing
| more CPU to be spent on collecting?
| pjmlp wrote:
| Wrong, Longhorn failed because WinDev sabotaged the
| project.
|
| https://news.ycombinator.com/item?id=30486727
|
| Likewise, Midori was powering Asian Bing cluster for a
| while, and even that wasn't good enough for WinDev.
|
| And in Fuchsia, same thing happened, the parts in Go were
| only taken away when the Go advocates that driven those
| modules were no longer around to argue for their
| implementations.
|
| Politics are more relevant than technical limitations.
|
| Finally, Azure Cloud OS and Windows together have surely
| more lines of C# powering them than Apple has been doing
| with Swift.
|
| And then there is Android.
| GeekyBear wrote:
| The leaked builds of Longhorn took forever just to boot
| on the hardware of the era. That's a performance issue.
|
| However, the internal politics of Microsoft certainly
| didn't help either.
|
| The stated reasons for removing the Go portions of
| Fuchsia and rewriting them in another language were also
| firmly based in performance.
| pjmlp wrote:
| Of course debug builds aren't the prime example of
| performance, that is to be expected.
|
| Performance problems get sorted out when people actually
| care to solve them instead of driving their own agenda.
| GeekyBear wrote:
| Sorry, but debug builds weren't the only builds that
| leaked. The performance was just never there.
| pjmlp wrote:
| Naturally you would answer that, from an OS that never
| had a certified GA build, great proof indeed.
|
| Yet the same group that sabotaged Longhorn, was quite
| keen in doing exactly the same stuff using a mix of .NET
| Native and C++/CX, which only failed due to the lack of
| migration path from Win32, and not much love for the
| sandboxing.
| GeekyBear wrote:
| Microsoft handed out builds at PDC. It's difficult to
| take claims that it didn't have massive performance
| issues seriously.
| aeldidi wrote:
| I agree with the sentiment, but does Swift actually see any
| usage outside of Apple's own hardware? I think Swift's
| reliance on such an optimization is justified since the
| hardware this optimization is applied to is the main target.
|
| While Swift's approach may be slower in theory, is it
| actually slower in practice? Just a thought.
| pjmlp wrote:
| Hardly, but having to design hardware to fix ARC
| performance, proves the point how much "better" it is.
|
| Depends how much one cares about performance beyond iOS GUI
| apps, specially when Swift's original goal was to become
| the main language across the whole Apple stack.
|
| https://github.com/ixy-languages/ixy-languages
| losvedir wrote:
| I always took GC for granted until I started coding in Zig more
| and had to worry about managing my own memory.
|
| One thing that Zig made pretty easy that's been sort of my go-to
| is using Arena allocations a lot more. It's pretty frequent that
| for a chunk of code I know I'll be allocating some chunk of
| memory and working with it, but then once that's finished I won't
| need it anymore. So just throwing everything into the arena and
| then freeing the arena afterwards works great, is fast, and is
| simple.
|
| Are there any garbage collectors that enable this sort of thing?
| I think it might be vaguely like "generations", but I think even
| those are inferred and kept track of in greater detail to know
| when to move between the generations. I'm thinking of somehow
| earmarking a scope to use its own bit of memory and to not worry
| about collecting within the scope, and then when you leave the
| scope being able to free it all.
|
| I'm sure there's more complexities that I'm missing. It's just
| that dealing with memory manually really makes you realize that
| the programmer probably easily knows expected lifetimes better
| than can be inferred. The only GC's I've used have been all
| "don't worry about memory we'll figure out everything". But maybe
| you could provide hints or something.
| jatone wrote:
| the strategy you're mentioning is also known as slab
| allocation. all GC implementations allow this strategy
| naturally.
| verdagon wrote:
| Some possibly relevant works:
|
| Pony has a separate GC for every actor. I suspect that in
| practice, these work a lot like arenas; since they're very
| short lived, I'd imagine that most actors just allocate a lot
| and then blast it away. [0]
|
| Odin (not GC'd) has an implicit "context" that has an
| allocator, [1] effectively decoupling the allocator from the
| code. One could use an arena allocator for any function they'd
| like.
|
| Vale (not GC'd) is taking this the context system bit further
| and makes it memory safe. [2]
|
| [0] https://tutorial.ponylang.io/appendices/garbage-
| collection.h...
|
| [1] https://odin-lang.org/docs/overview/#allocators
|
| [2] https://verdagon.dev/blog/zero-cost-refs-regions
| mirekrusin wrote:
| Sounds more like ownership annotation than generations.
|
| Generations basically takes advantage of the fact that recently
| allocated memory is much more likely to be deallocated sooner -
| ie. it's run time/age based.
|
| What you're describing with arena is more like ownership where
| siblings can't outlive ancestors - ie. deallocate when
| parent/root (arena container) is deallocated.
| ryneandal wrote:
| Had to do a double-take at the title, wondering what the former
| Duke basketball standout
| (https://www.youtube.com/watch?v=uH5ltiHSJqE) knew about garbage
| collection.
| sharken wrote:
| To me Garbage Collection in a language makes it easier to work
| with than a language without.
|
| As an example C# (GC) and Delphi/Turbo Pascal (No GC). Was
| involved in two projects where the one made in C# became the
| successful one.
|
| One of the absolute worst things about Delphi was the lack of GC,
| which took away developer time better spent elsewhere.
|
| Yes, C# had also other benefits, but having a GC is one of the
| crucial features to make a language popular.
| ak39 wrote:
| Delphi has legendary ARC not just with its VCL and object
| hierarchies with TOwner model but also with its wrappers of
| COM's IUnknown and QueryInterfaces. Yes of course, developers
| can still make mistakes about sequences and memory management
| but a well constructed class hierarchy and discipline to manage
| the freeing of resources immediately after they're no longer
| needed is the cornerstone of basic Delphi applications.
|
| edit: The objects that are IUnknown linked don't even need to
| be explicitly freed! There are several clever use cases for
| this for example changing TCursor from hourglass back to normal
| when a procedure/function is complete. It just works.
| travisgriggs wrote:
| I'm basically "whatever" on this. I develop in both Swift and
| Kotlin. I like the Swift language better. Definitely like the
| libraries better. But I hate the memory story in Swift. ARC is a
| half-GC. It's definitely different than malloc/free (which I do a
| lot of in embedded C land). But the stressing about ownership
| loops sucks. I like structs and what they enable, but I find
| myself choosing them over objects sometimes, not because they're
| the best solution for the problem in front of me, but because ARC
| sucks. And then after trying to avoid ARC using a struct, I end
| up biting the bullet and using a class, because sometimes a real
| object is the better solution. Kotlin's "almost struct" data
| classes are a better answer IME.
| azinman2 wrote:
| Is it really that hard to deal with memory ownership? What's
| situations are you in that it's so stressful?
| nu11ptr wrote:
| Being a geek who is somewhat into PLT and hobbyist languages, I
| go back and forth a bit on whether I want a GC or RC in my own
| hobby lang. For the longest time I was going to go with per-
| thread heaps that use a simple compacting GC, but only collect on
| allocation when heap starts running low, so collection wouldn't
| have to stop threads, the local thread would already be stopped.
| The plan was to use RC most likely for multithreaded shared
| objects from a shared heap as a secondary.
|
| Then I started writing Rust and I realized what I think is the
| same point Lattner has here: Ownership changes everything. If I
| can declare a single owner most of the time and track that owner,
| I only have to bump the counter when I add a 2nd (and 3rd, etc.)
| owner, which isn't nearly as common as single ownership. This
| gets rid of most of the issues with RC performance (which in
| naive schemes bumps the counter every time it calls a function
| and more). RC still can't use bump allocation unfortunately, so
| any tree building would likely need arenas in addition which is a
| bummer, but deterministic destruction is a huge bonus, so this is
| the way I'm leaning atm (if I ever get back to writing it that
| is!).
___________________________________________________________________
(page generated 2022-04-24 23:02 UTC)