4 ms·
This part... > for the high frame-rate, heavy animations this puts a lot of pressure on CPU and memory ...does seem to suggest that the "garbage multiplier" e
by notacoward 5y ago
This part...
> for the high frame-rate, heavy animations this puts a lot of pressure on CPU and memory
...does seem to suggest that the "garbage multiplier" effect of immutability is an ill fit for applications that also create a lot of garbage naturally. Note that this is about as close to an apples-to-apples comparison as we're likely to get - the same application implemented two different ways - so it's not the application's innate object-lifetime characteristics that are the problem. That's an implementation artifact.
The question is: how many applications are likely to hit this same limit? Is this a rare case, or is it common? If it's common, it is indeed an indictment of the "immutable" approach. Otherwise, not so much.
- xyzzy123 5y agoI've always thought of immutability as great for situations where you want to "explore" (clone complex current state and go do some "what if"), need internal transactions, or allow time travel (snapshot/undo/redo) - situations where the state sharing is both efficient and feels "natural". Also if the data/history are relatively small compared to the available memory it's a fine default that generally leads to "nicer" code. Video doesn't seem at first glance like such a great fit.
- kccqzy 5y agoVideo can be played backwards and forwards, can be sought, or jumped to a particular point in time. It fits well with the "time travel" benefit of immutable data structure. The author mentioned it was extremely easy to implement some kind of a checkpoint or key framing with immutability. That's exactly using immutability to its strength.
- titzer 5y agoThe downside of immutability is the sheer volume of data (in the form of pixels) that needs to be pushed around. A single 4k frame is 8.3 million pixels, so you are looking at over 30MiB of data for 32-bit color, and you gotta push 30, maybe 60 of those a second. Maybe if you have a really good garbage collector (or a custom one, because frames are all the same size) you can get away with allocating that much data and freeing it every second. But that doesn't free you from the fact that you are not utilizing hardware caches well; you don't get good spatial locality at the hardware level unless you reuse the same physical pages of memory for every frame. And you can basically only do that if you have a mutable design.
- littlestymaar 5y agoThis comment is completely off: obviously asciinema didn't store each individual pixel in its data structure. That would be a completely stupid thing to do even for a regular data structure instead of an immutable one…
- sickill 5y agoThat's not how asciinema-player works though. The player internally represents the terminal buffer as a grid of characters. So for 80x24 terminal you have 80*24=1920 grid cells, each keeping a unicode char + color attrs. When rendering the adjecent cells of each line are grouped by their common color attrs, resulting in (usually) a small number of span elements with text and proper style/class. You can see this in action by going to asciinema.org, opening a random recording, pausing it, then inspecting the terminal with browser's DOM inspector.
- titzer 5y agoSure, if you don't break it down to individual pixels, the data is way less. Ultimately, getting a well-performing GC is finding enough idle/spare/background cycles to scan memory and recycle it at a greater rate than allocation. If the GC falls behind then inevitably you are going to end up with a big pause. I don't think there's enough memory bandwidth to decode 4k video the naive way, but a small terminal will probably be OK. That said, it's still less efficient than just poking the bytes in memory.
- fnordsensei 5y agoClojure developers are not unaware of the tradeoffs between immutable and mutable data structures. You'll see them use mutable data structures, particularly in tight loops inside functions that take immutable inputs, mutate them, and produce immutable outputs (thereby preserving the promise of immutability, while leveraging the performance of mutability internally). You'll rarely see apps designed like this up-front though. Most of the time, the surgical mutability will come as a performance optimization pass later on. As for the apples-to-apples part, I for one am unsurprised to see that WASM performs better than ClojureScript, particularly for an application like this.
- derefr 5y agoNot being too familiar with Clojure, but being familiar with Erlang, I'm curious whether Clojure has any popular libraries that approach efficient mutability the way the Erlang runtime does. The Erlang runtime exposes complex mutable resources like ETS tables through opaque handles, where the handle can be freely shared, but the resource backing the handle can never actually be touched by "clients." Instead, the resource backing the handle lives in its own heap, which is owned by a manager object; and accesses to the resources in that heap are done by handing the manager references to data that it then copies into the heap; or querying it by handing it a reference to a key, whereupon the manager will copy data back out and return it. It's not really the same abstraction as e.g. a Concurrent container-class in the Java stdlib, as it's not implemented through the client ever acquiring (the moral equivalent of) a mutex and then touching the data itself; nor does it involve the client adding object references to an atomic queue, where some async process then weaves those references into the backing object. Neither the client's execution-thread, nor its passed-in data, ever touches the handle's backing data. Instead, ETS and the like have guarantees almost as if the mutable resources they hold were living in a separate OS process (similar to e.g. data in a nearby Redis instance), where you need to "view" and "update" the resources living in that separate process through commands issued to that server, over a socket, using a wire protocol; where that serialization over the socket guarantees that the data reaching the other end is a copy of your client-owned data, rather than a shared-memory representation of it. The same semantics, but without the actual overhead of serialization or kernel context-switching, because the "other end" is still inside the managed runtime of your OS process. And, to be clear, Erlang ETS accesses aren't linearized by a "message inbox" queue sitting in-between, the way that regular Erlang inter-actor message-passing is. The ETS table manager can handle multiple concurrent requests to the same table, from different processes, simultaneously, without locking the whole table—just like an RDBMS would. (Instead, it uses row-batch locks for writes, like RDBMSes do.) The concurrency strategy is a concern internal to each particular black-box-with-a-handle, rather than something general to the abstraction. The only thing guaranteed by the black-box-with-a-handle abstraction, is that nobody can mutate the data "inside" the black box without its manager's knowledge, because nobody ever holds a live reference to the data "inside" the black box.
- merijnv 5y ago> ...does seem to suggest that the "garbage multiplier" effect of immutability is an ill fit for applications that also create a lot of garbage naturally That actually depends on how your GC is implemented. For example, due to laziness+immutability, Haskell produces a lot of garbage and a lot of allocations. This is not a problem with the GHC compiler, as the GC design makes allocation cheap (effectively a bump pointer allocator) and GC cost scales with the amount of non-garbage (this is, like all GC design, is a trade-off that can get you into trouble with some workloads).