6 ms·
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
by sillysaurusx 5y ago
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 5y agoFor me the biggest benefit is deterministic destruction, specifically for the non-memory resources it is holding (files, locks, etc.) the same as if it was stack allocated. It is very freeing knowing that is just being taken care of. No 'defer', 'with' or other special handling needed. But possibly the bigger benefit: no heap allocation for many objects at all. If you can prove single ownership, you can stack allocate it (or "inline it" if in a struct/object).
- shakna 5y ago> No 'defer', 'with' or other special handling needed. Just as an aside - in Python, it surprised me that 'with' is _not_ a destructor. The object created will remain in-scope after the 'with' body, but the '__exit__' method will be called. Which for the canonical example of file-handling, only really closes the file.
- jnwatson 5y agoThe done vs destroyed dichotomy is a cross-cutting concern for programs that need explicit control over resource lifetimes. RAII is actually an antipattern in Python if you actually care about resources being released.
- shakna 5y agoThe surprise is less about resource usage and more about the scope lifetime. 'with' introduces a new binding that outlives its body. It looks, at first glance, to be a temporary variable. But it isn't.
- pphysch 5y agoConsider 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 5y agoI 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 5y agoPython 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"
- chombier 5y agoI 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 5y agoI 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/ https://peps.python.org/pep-0343/
- SemanticStrengh 5y agoWhat does it bring vs Java autocloseable?
- nu11ptr 5y agoJava'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 5y agoIf 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 5y ago> 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 5y agoI guess that is why no one bothers with clippy then, it is superfluous.
- tialaramex 5y agoEverywhere 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.
- oaiey 5y agoI 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.
- xxs 5y ago>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 5y agoLocks 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 5y agoThe 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.
- nine_k 5y agoThink 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 5y agoTo 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".
- kazinator 5y agoI don't know if there is a typical heap size. My TXR Lisp is garbage collected. When I blow away all the compiled files of the standard library and run a rebuild, in a separate window running top, I do not ever see a snapshot of any txr process using more than 27 megs of RAM: This is close to the worst-case observed: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 19467 kaz 20 0 27364 24924 3112 R 100.0 1.2 0:30.55 txr When this is happening, the library code is being interpreted as it compiles itself, it by bit. E.g. when stdlib/optimize.tl is being compiled, I see this 27 megs usage. But after the entire library is compiled, if I just "touch stdlib/optimize.tl" to force that to rebuild, it's a lot leaner; I see it hit about 16 megs. Sitting at a REPL prompt, compared to some bash processes, using ps -aux: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND kaz 2433 0.0 0.0 8904 1900 pts/0 Ss Feb22 0:00 -bash kaz 6179 0.0 0.2 9072 4212 pts/1 Ss+ Apr22 0:01 -bash kaz 18551 0.0 0.1 11152 3932 pts/2 Ss+ Feb22 1:32 -bash kaz 18836 0.0 0.1 9064 2196 pts/5 Ss+ Apr13 0:02 -bash kaz 19079 0.0 0.2 13464 4880 pts/3 Ss Mar01 0:40 -bash kaz 19849 0.1 0.2 9360 6024 pts/4 S+ 23:45 0:00 txr Oh, and that's the regular build of TXR. If that's too bloated, you can try CONFIG_SMALL_MEM. Large is as large does. Garbage collection was once used on systems in which most of today's software would not fit. Garbage collection does have waste. You allocate more than you need, and keep it. Precise, manual memory management does the same thing. Firstly, the technique of keeping around memory is practiced under manual memory management. E.g. C programs keep objects on recycle lists instead of calling free. And free itself, will not release memory to the OS, except in certain circumstances. Then there is internal fragmentation. Generally, programs that "get fat" during some peak workload will stay that way. If you see some garbage collected system need hundreds of megabytes of RAM just for a base image, it may not be the GC that's to blame, or not entirely: it's all the bloated crap they are pulling into the image. Or it could also be that the GC was tuned by someone under thirty who doesn't think it's weird for a browser with three tabs open to be taking 500 megs. GC can be tuned as badly as you want; you can program it not to collect anything until the VM footprint is past the physical RAM available.
- iscoelho 5y ago1. 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 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 5y agoA 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-concurrency https://verdagon.dev/blog/seamless-fearless-structured-concu... [1] https://tutorial.ponylang.io/reference-capabilities/reference-capabilities.html https://tutorial.ponylang.io/reference-capabilities/referenc... [2] https://vale.dev/guide/structs#mutability https://vale.dev/guide/structs#mutability [3] https://cone.jondgoodwin.com/coneref/refperm.html https://cone.jondgoodwin.com/coneref/refperm.html
- MaxBarraclough 5y agoI 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 5y agoThe 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.
- 12thwonder 5y agolike 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 5y agoCUDA.jl apparently uses Julia‘s GC to free up GPU resources, see https://cuda.juliagpu.org/stable/usage/memory/ https://cuda.juliagpu.org/stable/usage/memory/
- 12thwonder 5y agogood 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.
- kazinator 5y agoNo it isn't. Chris put his foot in his mouth and went on about how an advantage of garbage collection is that it moves objects. Not all garbage collection moves objects. Garbage collection that does not move objects does not have any such interop problem. Furthermore, even GC that does move objects can avoid that particular interop problem if it simply avoids moving foreign objects. E.g. suppose a foreign object is managed by some gc-heap object of type "foreign pointer". That managing object can be moved during GC, but if the actual foreign pointer doesn't change. If that pointer is all that the foreign code knows about, then there is no problem. Don't give foreign code any pointers to your heap objects and you're good.
- hmlongco 5y agoUh... I can solve entire batches of problems by just saying, "don't do that and you're good." Unfortunately, real-world constraints always seem to get in the way...