Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The immutable hype is finally fading. People starting to realize the drawbacks of treating hardware as an infinite resource.


What a boring takeaway from this. Beyond the fact that the immutable data structures proposed by Clojure/script tend to perform very well in a lot of "normal" cases (and in a lot of normal web-app workflows your stuff is immutable, like "query then display the result from an API"), at least to me it feels like asciinema is a very good example of a case where you have tougher-than-average performance requirements.

Not to say that we shouldn't have "everything be performant" but drawing a bunch of stuff to screens is _the classic_ performance question. Whereas most "business apps" people here work on to a day-to-day have different performance issues.

Rewriting your CRUD frontend in Rust isn't going to make your DB queries faster


Won't make them slower, anyway.

There are often performance bottlenecks you didn't know about, and had blamed on database (or whatever) interaction overhead. It will never feel worthwhile to dig into each candidate, because any payback seems too unlikely. Not having left scope for such bottlenecks means you can be confident they are not there. Re-implementing once is a lot less work than diving into each possible bottleneck. Improving your actual database operations, after, is more likely to have an effect when some other bottleneck doesn't mask the improvement.

You don't have to do it in Rust. Any optimization you could do in Rust is probably easier in C++, and also easier to find maintainers for.


> Any optimization you could do in Rust is probably easier in C++, and also easier to find maintainers for.

At least the first part is not necessarily true. E.g., in C++, you might make defensive copies, whereas in Rust the lifetime system will track things for you.


Thus, "probably".


"Any optimization you could do in Rust is probably easier in C++"

I think this feeling comes from the fact that it takes longer to learn the basics of Rust compared to C++.

However, once one has learned C++ or Rust to a reasonable level, I would argue that Rust is actually easier to use.

This is not the same thing but many people make this claim.


Each is easier than the other, depending on where you look and where you come from.

But it is a fair bet that changes to C++ code to implement a point performance optimization will be smaller than the same sort of change would be for Rust code. For the latter, you are likely to need to re-architect that part of the system some to get your optimization and still satisfy the borrow checker. Having a borrow checker that demands satisfaction is a virtue, but there is no denying it adds cost in the small, where we're talking about, notwithstanding that such cost may be paid back at the system level.


> it takes longer to learn the basics of Rust compared to C++.

Does it really? For example I'd think that initialization of objects is a topic that should be in "basics", yet initialization of objects in C++ seems disproportionately complex compared to Rust (at least to me).


Yet, object initialization is not a thing anyone needs to pay much attention to. Yes, there are historical rabbit holes, but you need not go down them.


> object initialization is not a thing anyone needs to pay much attention to

So it's perfectly fine for me to leave objects uninitialized because of lack of attention?


It's perfectly fine to initialize them in the simplest and most obvious way.


> Any optimization you could do in Rust is probably easier in C++

That's kind of funny in light of the history that certain optimizations in web layout engines were attempted, unsuccessfully, in C++ multiple times and ultimately they invented Rust to make them easier.


That is how the marketing goes, anyway.

The facts on the ground probably have more to do with improvements to the C++ code being obliged work as deltas against existing C++ code, where the Rust code was a complete re-implementation, thus not constrained.

Both C++ and Rust are today different languages from when that project ran.


Another subtle consideration is that with long-lived, highly-backward-compatible languages like C++, you'll have a bunch of people on your project who still write C++ like it was ten or twenty years ago (because that's when they learned it), and so bring down the code quality of your project. Whereas choosing a new language (like Rust at the time) means that everyone who claims to be able to write it at all, is working from the same, recent set of language idioms.


> Rewriting your CRUD frontend in Rust isn't going to make your DB queries faster

A typical consumer disk can do 1,000,000 IOPS (enterprise is one generation behind, and slower at the moment), with millisecond read and write latencies.

Are there any managed CRUD frontend languages that are fast enough to keep up with that?

(By “keep up”, I mean “be less than half the hardware I provision at scale”)


On the other hand, rewriting the DBMS itself in Rust might. Especially if the DBMS was originally written in Clojure (see: Datomic.)


I'm not Datomic's biggest fan, but you know it's a hosted database with multiple backends, right? A lot of the heavy lifting is delegated to storage like DynamoDB or Postgres.


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.


Clojure 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.


Not 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.


I'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.


Video 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.


The 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.


This 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…


That'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.


Sure, 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.


> ...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).


This is an application where the goal is to squeeze every last drop of performance possible out of the processor. The "every last drop of performance" crowd has never been the source of the "immutability hype". Your comment is dismissive of many domains of programming - immutable didn't become a thing because others weren't as enlightened as you.


> This is an application where the goal is to squeeze every last drop of performance possible out of the processor.

I have a hard time thinking of an application for which this isn't the case. If my cooking recipe app / website is too slow and/or eats too much battery (and god fucking knows they are) I'll look for a competitor immediately.


not being too slow and "squeeze every last drop of performance possible" is not really the same, and the latter is an expensive tradeoff to make.


This talk by Richard Feldman about Roc (a new language) goed into why immutable does not necessarily mean slow and given enough attention can mean higher performance in certain cases: https://youtu.be/vzfy4EKwG_Y


Well the immutability hype allowed the author to build the previous incantions of the library. At this point, I hoped everyone understood the tradeoffs of using abstractions. Otherwise we should all be programming on assembly.


This. If the project was reimplemented in assembly, it would be faster and smaller and the same conclusion could be drawn: “finally the high level programming hype is over”.

come on..


mutability is a data structuring virtualization but i'd just as much suspect the runtime virtualization.

that the bundle used to be 570kB isnt an immutability issue. itcs that clojurescript drags in a whole clojure runtime, a new virtualization layer atop the js runtime. that, to me, is the most likely suspect.

that said, for sure, short tbeow away high gc allocation patterns are generally not good. at work there's a lot of "functional" patterns, nary a for loop in sight. this endless .map() .filter() usage causes near exactly similar issues, with shortived objects. it seems ultra sad & silly to me. waste after waste. but i also think we have much more deeply rooted problems.


Immutability is a drag if you create lots and lots of referenced state because, e.g., you want to hold on to many snapshots of past state.

Immutability done right need not be much worse than mutability.

For example, jq's internal value representation is immutable in appearance:

- mutations yield new values,

- but when the value being "mutated" has only one extant reference then mutation is done in-place,

- while when the value being "mutated" has more than one extant reference then mutation is done by copy-on-write.

If you manage to always have extra references, then "immutable mutation" gets expensive.

If you manage hold on to old references for a long time, then "immutable mutation" gets even more expensive.

In a run-time with a GC not based on reference counting you do have to GC all the intermediate garbage.

Immutable data structures really lend themselves well to reference counting GCs because you can't have cycles in immutable data.


> Immutability is a drag if you create lots and lots of referenced state because, e.g., you want to hold on to many snapshots of past state.

Isn't it the opposite? If you want to hold on to many referenced states at the same time, an immutable data structure should provide less overhead than a mutable one, due to structural sharing.


A performance drag. Let's say you have an app that for N inputs creates N^2 state in memory...

But now, if that's just what you must do for some reason, then, yes, immutability makes the task of taking all those snapshots real easy.


I should add that reference counting GCs are nice because short-lived garbage is freed immediately and there's no need to look for garbage, so they're much faster than scanning GCs. Reference counting GCs can have GC-like pauses when releasing objects that have singular references to many many many other objects, but the same is true in manual memory management systems.


Are you sure about the short-lived allocations being a problem for the collector? My understanding of modern generational garbage collectors was that they performed quite well with short-lived garbage. Not as well as not creating the garbage in the first place, of course, but not so badly as to be a problem in most cases.


"Perform quite well" is always relative to some reference. The oldest trick in the book is comparing to something even slower and saying "see? fast!".

GC apologists seek to normalize this behavior. They often succeed, at that. Performing quite well against actually fast things, less often.


You seem to have an axe to grind.

Performance isn’t black and white. Optimizing your memory usage isn’t going to do you a whit of good if you’re constrained by your database queries. Optimizing your DB queries isn’t going to do you any good if you’re constrained by a chatty microservice architecture. Optimizing your UI response time isn’t going to do you any good if you’re already below the threshold of perceived speed. Etc.

GC performance on short-lived objects is quite good in enough situations to matter, such that optimizing for it, rather than your application architecture, is likely foolish outside of performance sensitive loops.

Time is always limited. Spending your optimization budget on micro-optimizations is short-sighted.


Meaningless personal criticisms undermine your argument.

Performance problems usually appear in places we prefer they would not, often runtime apparatus we poorly control such as GC. It is always preferable to try to ignore and discount those, as they may be arbitrarily hard to fix, so people do.

Yet, actually not depending on such apparatus, where it is the problem, gets you free optimization.

Performance doesn't care where it is found or lost. Micro-optimization is foundational; fail there, and there is often little else you can usefully do. The best optimizations are not doing the thing at all. GC is always strictly worse than no memory management.

Fixing your chatty microservoices and your under- or over-indexed DB queries may do you no good if you have built in bottlenecks of your own.

"Quite good" means nothing except in comparison to something else.


I want a world without garbage collection. I don't think we can achieve a world without garbage collection while there are codebases based around aliased pointers (which makes lifetime reasoning difficult, and causes enough use-after-frees to make the Linux desktop apps I use unstable, unless you use refcounting) and circular references (which is difficult to refcount). Maybe I'll wait for Rust programmers to rewrite software around tree-based ownership (restrictive but immune to these problems) and ECS/arena indexes (can use-after-free but won't segfault).

In the time being, while "spiderweb object graphs" are commonplace, perhaps Nim's memory management (https://nim-lang.org/docs/gc.html) can give us "non-GC by default, refcounting or GC when necessary". I want it to succeed. I hope it does.


This is true for small amounts of garbage but not for large amounts which is still slow and large is relative to the hardware your user is running on. It’ll also cause a death by a thousand cuts where it’s extremely difficult to dig yourself out of poor performance because the cause runs throughout the program.

It’s better to look at cheap short lived collection as a great way to get the thing you’re making working but ultimately something that needs to be cleaned up to be production ready.


Clojure solves these issues with unneeded garbage creation in algorithms with the transducers functions.

And you can of course just use java objects whenever you want.


This article doesn't really support that. ClosureScript targets JavaScript, which is a platform that doesn't have special support for optimizing immutability.


How would one optimize for immutability in this case, other than turning it back into mutability behind the scenes? I've certainly seem some code written in an "immutable" style where it was pretty clear that the intent was for one data structure to be a mutation of another, just called something else because the language required it. That case might be easy to optimize ... but the general case?


I'm not an expert on this, but one example is that immutability lets you operate safely on things in parallel. But JS VMs are not aware of objects that are immutable in Clojure's semantics, and in any case, do not operate on objects in parallel anyhow.


> How would one optimize for immutability in this case, other than turning it back into mutability behind the scenes?

Roc-lang, which is a functional, systems language in development uses something called opportunistic in-place mutation to do just that. Here's a video where the creator talks about it: https://youtu.be/vzfy4EKwG_Y?t=1276


Generally in compiler technology, immutability is an important tool in letting compilers reason about and make program transformations. See eg the "single static assignment" intermediate-representation form that is mainstream in low level language compilers. But SSA form isn't as good as having the original program expressed immutably, because you get false or incidental data dependencies if the compiler has to conservatively derive the optimization-friendly SSA representation out of the original non-immutable code.

In practice a JS implementation that had special optimizations for code using immutability as a convention might for example auto-parallelize code.

Also it by no means a bad thing if a compiler turns a piece of easy to reason about functional code "back" to generated code that exploits local mutability behind the scenes in some circumstances, that's exactly how we want it to work. We still get the robustness guarantees of semantics where our objects don't change from under us in programmer visible ways.


Talking about parallelization opportunities is a bit pointless when you need 50 cores to match the performance of the single threaded code (in the best case, assuming perfect scaling).


Fair enough, but parallelism isn't just about cores since SIMD can also help. Also, immutability doesn't just help through parallelism, it also allows other things (as another example, avoiding redundant work like loads of an immutable field and computations on them).

Would that help with a huge 50x difference? Maybe not, but the point is that evaluating the benefits of immutability on a VM that does not optimize it - JS VMs - is not relevant. (And that 50x might also be caused by other limitations of JS VMs and not immutability at all, like say deopts.)


I disagree - lots of compiler work is done on languages that aren't best in class for high performance work. Look at all the effort that people are putting into making Python faster. And indeed JS itself, it wasn't always this fast. High level language users live by "There's more to life than increasing its speed" and then if they take off, people come work on performance and make them faster.

Also let's not forget that this was already "fast enough" for a long time before the 50x rewrite.


My issue was specifically with the claim that persistent data structures are great for parallelization.

I have absolutely no issues with efforts to improve single threaded performance of programming languages (and indeed the advances made by JS are remarkable) and I don't believe any language needs to be "As Fast As Cee". But There are other perfectly reasonable languages that are within a small integer factor of C and C++. You do not need to pay a large penalty for ergonomics.


These techniques are also applied by compilers such as clang.


Pretty bog standard behavior for a compiler backend translating immutables in IR. Including a runtime compiler into app code is the next stage in the evolution of cycle burning leetness.


I fantasize about a future where we have enough CPU and memory that we can waste them on nice stuff like immutable data structures and software rendering.


I fantasize about a future in which buying a faster computer means my software runs faster.

I don’t spend thousands on computer hardware so that lazy devs can get lazier.


You may call functional programmers who prefer immutable data structures lazy because we want to actually understand what we create, but I don't see how the 10 billion layers of abstractions and state duplications somehow end up making better software.


I agree - I think having 10 billon layers of abstraction is worse for everyone. It’s buggier, more expensive to make and (ironically) often slower in practice anyway; because you can’t optimize what you can’t understand. Java style OO has a lot to answer for.

Functional programming is great. What’s not great is loading the entire closure VM environment into my browser - resulting in the software running (in this case) 50x slower. FP is no excuse for making my computer crawl to a halt.

And there’s no essential reason FP needs to be slow. For example, look at how well llvm optimizes map/filter/fold in rust. In many cases they’re faster than their imperative counterparts! There are other ways to benefit from immutability that don’t involve burying a garbage collector in useless work. For example, React is a lovely example of immutable ideas, and a fast runtime.

I spent a few thousand dollars on a new Mac laptop recently. I wonder what percentage of my clock cycles are going to be wasted due to bad abstractions and inefficient code? Probably most of them. I wish I could take the money I spent on the machine and instead pay people to improve their software. I don’t have billions of cycles per second of actual work to do.


> What’s not great is loading the entire closure VM environment into my browser

There's no VM in clojurescript, it compiles to JS, it is tree-shaken and heavily optimized and minified through Google's Closure compiler.

> resulting in the software running (in this case) 50x slower

The speedup is not a result of abandoning clojurescript, it's from moving from immutable data structures to mutable arrays and primitives. The same can be done in clojurescript or javascript.

I think this is a common misunderstanding, that's why I'm reacting. Nobody claims immutable data structures to be the silver bullet. Computation-heavy parts need to be done in low level code and with primitive types.


> The speedup is not a result of abandoning clojurescript, it's from moving from immutable data structures to mutable arrays and primitives. The same can be done in clojurescript or javascript.

More or less, yes.

The majority of the perf increase here came from two things: 1. going from immutable->mutable, 2. going from CLJS/JS->Rust in the perf critical part. Doing just 1. would likely improve the performance, but not as much as doing both 1. and 2.

Doing just 1. while staying with ClojureScript could potentially be accomplished with transients [0] at the cost of making a major chunk of the code non-idiomatic Clojure. I actually played with transients here before attempting the rewrite, but haven't got too promising results though.

[0] https://clojure.org/reference/transients


I'm not sure if you feel attacked or not but you shouldn't. Being lazy is good for programming I think. You should keep in mind that your functional programming base is built on a figurative 10 billion layers of abstraction already. It's just that those abstractions are somewhat well made so you don't have to think about it.

Proper abstractions aid in understanding and can ideally be optimized away. Poor abstractions hinder it and slow things down.


To me, it is lazy to use immutable data structures in situations where they generate large amounts of garbage and/or result in user-facing GC pauses. (In Firefox, I encounter many slow sites (with or without immutability) with multi-frame pauses, often GC pauses. I think the slowdown is coming from the website and not my extensions.)

I believe it's possible to understand the code you create, even in the presence of mutation (though you can no longer store old values for free, and need to use cloning or other approaches). You need to restrict which code is responsible for mutating state (using careful program architecture), and restrict the ability to mutate data while other pointers to the data exist (Rust imposes these restrictions). Interestingly, the Relm architecture is a translation of the Elm architecture (Elm is an immutable language) to Rust code (Rust is a mutable language) which restricts which code is responsible for mutating state, and Rust restricts the ability to mutate data while other pointers to the data exist.

Interestingly, Rust unifies immutable and mutable data structures. The im library (https://docs.rs/im) uses the same tree-based immutable data structures as Clojure and such, but exposes an API closer to Rust's stdlib containers (including mutation). However im's performance characteristics are different from Rust's stdlib; clones are shallow and take O(1) time, while IndexMut is slower and copies tree nodes whenever that node's refcount is greater than 1. immer.js (https://github.com/immerjs/immer) has a somewhat similar API, but a different implementation (I think it uses standard JS arrays and copies the whole thing when you mutate one element).


I wouldn't characterize hoping to use immutable data structures and software rendering as trying to be lazy


I would.

Instead of properly managing mutable state (which can be difficult in situations with growing teams and complex application logic) people are opting to just copy everything or copy a subset of the tree they need so they don't have to think about it.

Immutability is bad for performance when the purpose is not recalculating a certain state after a certain number of operations(memoization). Many of the best practices that have cropped up in the past few years has been more for helping teams of people deal with growing code bases rather than helping programmers deal with limited hardware. In computer science this should be self explanatory. For optimal runtimes the act of making copies is avoided. Why people usually seem to ignore the fact that they're wasting cycles for the sake of the holy grail of clean, functional programming and immutability has eluded me.

Typescript, focus on immutability, microservices even. I hate all of them but they have their purposes. They solve people problems. Maximizing hardware performance is not in the list though.


Abstraction is at the core of programming... complaining about people not "properly managing mutable state" is like complaining about choosing Java over C because they're not "properly" managing memory and instead using a garbage collector. Maximizing hardware performance is, truth be told, largely irrelevant for the vast majority of applications. If they can meet their goals/deadlines/whatever, you can call them lazy, but I think most would call them efficient.

Though I must admit, every time my browser lags when viewing what SHOULD be a static site, I do die a little inside.


Yeah now I wouldn't use immutable structure in an performance intensive application, but I do _hope_ one day we have some cycles to spare and can use it without worrying too much. To me it's like garbage collection, it's nice to have if we can afford some performance cost.


Yet, we may characterize lazy programmers as hoping to use immutable data structures and pay no attention to performance.

We all know that performance takes extra work to ensure, then, and is uncertain even with the extra work.


The use of persistent data structures can make concurrent programming easier, which allows for better use of many cores. Functional programming can also scale to many computers in distributed systems, e.g. in Erlang.


That ship sailed three decades ago.


You cannot physically click faster than about 100ms in reaction to a UI. A proper application that isn't 100x less efficient will never be noticed by you unless you go out of your way to measure it. Stable software is more important than unusable fast.

"Lazy devs", Work on a team in C graphic code and watch nothing get done.


Comparing to C is always pointless. There is no environment constrained to using C for performance. There are only individuals who refuse to move on from C.

It doesn't matter what language they move on to. Rust and C++ are both good.


A 100ms delay is trivially noticeable - I suggest trying to type with that delay. In terms of video people reliably distinguish between a 144hz and 240hz refresh rate which is a difference if just ~3ms.


Ever played a FPS game with 100 ms ping? Then compare that feeling to LAN latency.


That future is our past. The era of free, continuous order-of-magnitude single threaded CPU improvements is well behind us. Performance is only growing very slowly. On the other hand ram, disk, and network bandwidth/latency is improving continuously, making the CPU even more of a bottleneck.


Welcome to now. Totally works like 99.99% of the time.


The benefits of immutability are still there though. You should generally design immutable by default and use mutability for performance where it matters.

Rust which was used here also has immutability by default and mutability is an explicit opt-in.


What is the connection between mutability and excessive hardware usage?


As an oversimplified example, in using an immutable screen buffer, a change between frames results in allocation of a full buffer rather than overwriting memory within the buffer (though one could probably think of ways to optimize this for the use case). Excess comes either in the form of unnecessary (relative to mutable) memory usage and/or CPU cycles necessary to support high-levels of garbage collection.


> in using an immutable screen buffer, a change between frames results in allocation of a full buffer rather than overwriting memory within the buffer

That’s actually not how clojure’s immutable data structures work. They use structural sharing, so only the portion that changes (roughly) needs new memory allocated, and only the parts that changed get garbage collected, so it is a bit more efficient than that.


You can't deallocate part of a frame buffer, it's a single allocation. Unless every pixel is individually allocated, which would clearly be insane.


But if your frame buffer is text, you can store the text in a rope data structure like JS vms do

So you don't necessarily have an allocation for every single character, but you're still able to share memory between buffers

I've implemented a game engine with immutability (makes for fast cloning in AI search) where much of the game state is shared between clones. With reference counting it also means if there's a unique reference being modified then no copy is made. This same trick is used by Python to optimize string concatenation


I'm explaining how clojure's data structures are different from OP's screen buffer example.


that's only because js is "stupid"

if you have the string "hello" and somewhere else "hello world" you can retain only one copy of "hello" because it's guaranteed it won't change.

but js vm it's not smart enough for that.


You're wrong, JS has immutable strings & so VMs use ropes to make mutable usage & slicing fast

https://gist.github.com/mraleph/3397008 https://twitter.com/rauschma/status/1269964154275799041


thanks

my bad, I learned something new.

anyway this proves that immutable data structures are not inherently slow, this is infact an optimization that makes things faster.


or maybe is JavaScript hype and the make concurrent programming impossible that it's finally dying.

Immutability is alive and well, it's simply a matter of js runtime not supporting it, because the developers thought "one thread ought be enough for anybody".




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: