Hacker News new | past | comments | ask | show | jobs | submit login
4x Smaller, 50x Faster (asciinema.org)
758 points by pama on Nov 30, 2021 | hide | past | favorite | 200 comments

It's awesome to see performance-oriented projects to find their way to SolidJS(https://www.solidjs.com). So satisfying to see success stories like this one. Great work.

Congrats! One nit I have about SolidJS documentation is that it doesn't have a clear path for "migrating from React" which I take it is probably actually important to you given how similar SolidJS looks to React and how much faster it claims to be. Also might be good to include a section on how to interop with existing React libraries -- even if the answer is that you can't.

And it might be worth putting in your HN profile that you are the creator of SolidJS. :)

Yeah it is sort of buried and probably not very clear: https://www.solidjs.com/guide#react.

And good point thank you about updating my profile.

Wow, I just read about Solid for the first time, and I'm very impressed at the API design. I love how it's actually a fully reactive data flow thing, but it looks and feels like React Hooks.

The other reactive/observable-based frameworks I've seen (eg Cycle) very much put the observable streams center piece, and I always felt that was distracting, and that nuances about how the underlying observable stream library worked quickly got in the way.

Solid still puts the components firmly at the center, just like React, but replaces React's state concept by fully reactive observable state, called "signals". You use them pretty much like you useState in React, but deep inside it's an observable stream of changing data, and you get all the finegrained update control that comes with that.

Also, I love how noun-heavy it is. Resources, tracking scopes, effects, signals. It's just like how React moved from "do this thing after the component updated" to "ok we have this concept called an effect", but extended to more topics such as dealing with async data loading, when exactly a signal is observable, etc.

> but it looks and feels like React Hooks.

I am confused about why this is a positive.

React Hooks are the reason why I want to stop using React. They are confusing and seemingly magical, compared to lifecycle methods that make a ton of sense. While I agree they can make complex things easier, they are also incredibly easy to get wrong, as they are order dependent.

Reading the Solid landing page, what I see is "Solid takes the most confusing part of React, and runs with it."

Reactivity like this predates React Hooks. It doesn't have the hook rules/stale closures etc... No dependency arrays, useRef, or useCallback. It's a very powerful model and very different. The similarities are surface level but aren't without benefit. Composable declarative data patterns, read/write segregation, traceable state dependencies.

Some people position it more like, "Solid makes Hooks the way they should have been in React". Personally understanding how React works this doesn't make sense. But I think it might be helpful for people just approaching the framework.

I'm struggling to find out how it actually updates the DOM. The way that re-rendering happens is clear but it looks like the top-level function just returns a JSX.Element. There is no explanation how this is efficiently rendered.

Also how are mid-level invalidations handled? For example if I update a list to remove one element do the other elements get re-rendered or are they cached?

It might be because I am browsing the docs in mobile but I find them hard to navigate.

I've been keeping an eye on your work for a long time now. We're a mobx shop so I was hoping to see you explore the ideas you had around that a little more (https://github.com/ryansolid/mobx-jsx).

I like and know where I'm at with React, but bringing a beginner through it recently definitely made me re-appreciate how nuanced it is. Also, you have to do a bit of voodoo to get good performance, and when you do the intention of the code vanishes pretty quickly.

For someone using React on top and mobx stores in the background (say 50k LOC all in), how big of a task would you say it is to move to something like Solid?

congrats on the performance. But I just hope that React will quickly catch up and improve in terms of speed & performance. It is really tough to learn a new framework every few months.

It's unlikely. Solid has had this performance profile since 2017. React hasn't really budged. Fundamentally different approach. There are different types of performance to explore but raw performance for small things is not something React is going to "catch up" on. Might be worth a read: https://javascript.plainenglish.io/javascript-frameworks-per...

Not going to happen. The key difference: virtual DOM.

I hope React won’t have all of those gotchas which make a good year of experience necessary when training a junior, until he migrates to a job that pays for his upgraded skills… Cost of skills is a thing.

Going to be very different in React 18

Thanks Ryan! And thanks again for your work on Solid.js.

> ClojureScript is not that easy to integrate with the JS ecosystem. I know, there’s been a lot of improvements done in this space over the years, and I’m sure someone will immediately point me to relevant docs, but it’s still the extra mile you need to go when compared to regular JS codebase

This always kills me. Clojure(script) is one of the neatest languages I've ever used, but it is just such a pain to work with. I spent hours getting NPM imports working in a project, when it ought to take seconds. It really makes it hard to recommend, even though the language itself is amazing.

I have also spent hours getting npm packages to work in JS before.

I just wonder why not just write straight JS? Adding a layer on top just means you have more complexities and steps to worry about. And clearly, in this case it was a big compromise in terms of performance and bundle size.

You'd have to torture me before I would make a conscious choice to pick JS over Clojure.

It sounds like that's exactly what happened to the GP.

GP didn’t pick JS for a rewrite either, so yes, I’m pretty sure GP is with me on that.

Yes they did, maybe take a look at the repo, the core vt layer is rust everything around it is in js(it's not cljs, you might ponder why not).

Well sure, but the important parts aren’t in JS. I can understand the argument that to provide npm library you may need to touch that garbage of a language, I’m just happy others do it for me.

The entire project used to be cljs, so obviously it's done on purpose, that is stated in the article.

what is done on purpose?

Yep, the author learnt their lesson. I bet they won't be using cljs again, not even for non-performant apps.

Perhaps, you should ask a question, why didn't the author reverse the question? Something like "How on earth was my implementation in a JITed language 50x slower on a warmed-up benchmark?" Where is the output of the profiler showing the exact bottlenecks? Of course, you can look at the repo and deduce some stuff, but it is a good habit to mention some key points about the environment such as compiler/ language/ browser versions, compiler settings, the hardware used etc.

Could he use more appropriate data structures? Could he avoid all the schema stuff that doesn't really improve the readability? Could he use better data structures later avoiding slow functions like update-in and migrating the bottlenecks to transducers and transients perhaps?

The author just did a rewrite and that is totally ok. He is trying things out and that is also quite alright. He provided some rather high-level benchmarks that would be really time consuming the reproduce and explain in more detail.

We have looked at the cljs code (e.g. https://github.com/asciinema/vt-clj/blob/master/src/asciinem...) with my colleague and it definitely isn't the best possible Clojure(Script) code around from a readability nor it seems performance standpoint.

To summarize, good that @sickill got a discussion going but it is best to step back and think about it in more depth. We all should apply more of this "extraordinary claims require extraordinary evidence" https://en.wikipedia.org/wiki/Sagan_standard Yes, it is his free time and good explanations/ understanding take time. We should treat the blog entry accordingly. It is a good exercise in critical thinking and code review, if you actually take the time to at least run through the code briefly.

Performance was not his only pain point. He also had issues integrating with the JS ecosystem. IMO Clojurescript is not worth extra layer of complexity.

We are using shadow-cljs https://github.com/thheller/shadow-cljs and that integrates with npm just fine. It doesn't get much easier than that I guess and if you have trouble, you can contact Thomas Heller or other people on the Clojurians Slack-channel.

If I did any app or system, that could be written in Java or JavaScript (browser or Node.js) I would take Clojure or ClojureScript any day. I don't know how to match the comfort and power in a different language. If it turned out e.g. by using a profiler, I just need way better performance in some bottlenecks and after I have exhausted all options that a) a better algorithm b) better technology/ different approach such as Canvas/ WebGL you name it in addition to what Clojure(Script) performance features offer, I would perhaps consider learning to write a module in Rust/ WebAssembly or just use a bit more resources (if this was e.g. a money problem).

Besides embedded, really high-performance stuff, some parts of the infrastructure that need to be as efficient as possible or run without GC or need to talk to very special interfaces or a library, Clojure and ClojureScript or related dialects/ implementations are suited for pretty much everything else I can think of.

We should be thinking about how to implement a Clojure-like language in more places, perhaps even without a GC but with AOT compilation + interpreter for the REPL along the way of Babashka. We should explore how to have a REPL to multiple systems at once and handle them like it was a single machine to some degree. We should be thinking, how to make a running Clojure program interruptible easily (perhaps with an extra setting), like it was a program in the shell. We should think about adding a Clojure-like language to the browser natively so that programs don't have to load it like they do now and that a browser tab could have some kind of REPL that you could authenticate and connect to over a socket. That way, you could rewrite the code of the web app at runtime if allowed by the user. And we definitely should design more APIs in much simpler way working more with data and less with invoking some specific functions. E.g. browser "history" could just be a vector/ array of maps/ objects or whatever instead of some finicky getters and setters that obscure the problem and are just another thing you have to learn to do useful work.

Fable is becoming the best way to compile to JS.


While I applaude the engineering effort that went into the project, I really dislike documentation that uses asciinema for regular non-interactive CLI interfaces: instead of showing me the commands in an overview I have to sit through the whole thing.

Documentation does not belong in asciinema. It’s for presentation, and using it as an excuse not to document is pure laziness.

Those same authors would probably have used a youtube video instead, so asciinema's existence is not the cause of this.

It's fine for a quick demo of a CLI application, but documentation and usage instructions should follow.

It's a lean-back experience, like the difference between a book and a video. It does have its perks.

I think it can work well as a demo, but I wouldn't call that documentation.

If you need to find something out, having to wait or randomly click through a video is extremely frustrating.

If it's a video, it does noe exist for me. I could watch them, but I have better things to watch if I want to watch something. So I move on to something else.

Most of us read far faster than someone umming and ahing through a presentation in a monotone can speak.

At the very least, give us a frigging transcript.

A transcript would be great, but it struck me in these days of deep learning it's just a question of time before someone does a "speedup" option for videos that instead of just increasing the speed of the video, combines that with "smart" cuts like removing umming and ahing and unnecessary dead space, and tries to apply other simplifications as well (I'd still prefer text, but I'm sure there'd be an upside for people who like video).

And imagine even a more aggressive "video summarizer" generating articles from videos with interspersed screenshots or brief video segments where they matter for the understanding...

Ew, people are using it for docs? It always seemed clearly to be for those quick-look demos on projects' home pages, and it's fantastic for that.

Asciinema is used for docs-ish in git-branchless's readme: https://github.com/arxanas/git-branchless/blob/master/README...

It does have formal docs, but I didn't fully understand the program after reading them. I didn't enjoy sitting through branchless's videos or clicking around for the right point in time.

What exactly is the advantage of asciinema over a video or a GIF if you're just showing something? If I don't need to copy text out of it, it has the same function as a video.

It's rendered by your browser so you get properly subpixel-hinted text instead of the horrible, blurry, compressed screenshot effect a video or gif gives. Also it's more convenient to generate than faffing around with screencasting software.

Edit: well, not (lossily) compressed in the case of a gif but not as nice as text properly rendered for your display.

I'd guess it's the bandwidth and compared to a GIF, you can pause them. While it's not usual to copy text from it, I've done that once this month and thought it was neat to be able to do that. I do believe that they have an advantage over video/GIF.

It's definitely better than a GIF because you can seek forward. You never know when a GIF ends, and if you're distracted, you have to wait for it to loop back again.

Note that this is a function of the GIF viewer. An image / video viewer that can play GIFs with length, speed, and movement controls would afford this.

vlc seems to do this (Android version).

I'll grant that the usual methods of interacting with GIFs don't do this, and am not arguing that they do. But if you really want a specific functionality, you can look for a tool which might provide that.

It has, if the thing you are showing fits the media. If you show the different flags of ls without explaination you would be better of just showing me the printout of ls -h

If you are showing off some beautiful TLI, an interactive prompt or ascii animations, this is the perfect tool.

As someone who works in film: film also doesn't lend itself to everything. Certain internal observations that work great in literature would be unwatchable on film etc.

Or to take it to the extreme: you don't expect the overview of an database to be experimental performance video art.

> If you are showing off some beautiful TLI, an interactive prompt or ascii animations, this is the perfect tool.

If you want to just show something off, a GIF (or real video) is the answer.

The defining feature of asciinema is that you can copy the text out of the animation. Which is actually not that useful most of the time, because you can't search for text in asciinema (AFAIK?) like you can in, well, documentation.

> a GIF (or real video) is the answer.

Why so? You can't pause or seek GIFs. GIFs and video files are usually several times larger than asciinema recordings of the same content. And you can't copy text out of them when it is useful.

asciinema is for showing off, specially TUI interfaces.

It's funny how it starts out with "immutability is really fast and GCs are soo good" and ends up with "rewriting everything in unmanaged code made it 50x faster".

Similar how "Ruby and Python interpreters are slow but webapps are IO bound anyway so it doesn't matter" to "how can I get this to handle more than x req/sec, can we get a JIT to speed up our dog slow backend".

One of the arguments high-level language proponents always made was that you could do exploratory programming and prototyping and develop your systems in nice easy high-level languages, and then FFI out or reimplement parts in the painfully low-level languages when you discover you need their performance.

asciinema prototyped and worked out their design, and discovered it needed more performance. And then it got it. Sounds like a success story to me.

> "Ruby and Python interpreters are slow but webapps are IO bound anyway so it doesn't matter" to "how can I get this to handle more than x req/sec, can we get a JIT to speed up our dog slow backend"

Turns out that if you write business logic with abandon, you end up with a lot of business logic.

Personally I wish that Python, Ruby and the ilk all get replaced with Lua, but also that Lua gets a proper `null`.

and array numbering from zero

No idea why this bothers people so much. How often are you manually indexing into an array with numeric literals?

This is why I've never been able to do anything serious with Lua. Small thing, but fatal for me.

It's almost like one side had the ability to say "we told you so" and they never bothered to :)

Historically asciinema was not easy to embed in React because you can't have multiple copies of React. So my team at the time wrote and open-sourced an embeddable alternative to it. Unfortunately that project seems to have disappeared by now.

But now that asciinema is no longer in React maybe it will be possible to embed now. See https://github.com/asciinema/asciinema-player/issues/72#issu....

Yup. No more React.js conflict. That was caused by the way how ClojureScript bundle has been built (reagent's copy of React added to the bundle's global/window namespace).

Also, the dependency on Solid.js is unlikely to cause any conflicts even if you used Solid.js yourself in your app given the new package doesn't globally export anything else other than the minimal public API for mounting the player in DOM.

Historically, you could embed an animated .gif into a web page going back before Y2K, and there are tools to make such a thing from recording terminal sessions. No Javascript, no third party websites.

Those are pretty heavy unless it's only a second or two (then why would you need a gif at all?) You'll be further cutting your target audience to the extremely privileged. I've had to suffer with dial-up (3-4 KB/s) until 2009, and with shitty ADSL (15-30 KB/s) until 2013. Many parts of the world are still like that.

I have a here 90 second .gif I made a year ago (demo of a particular Vim syntax highlighting scheme for a commit message format). It contains 930 frames, 10 fps. The resolution is 1200x768. The .gif is 600.0 kilobytes, so the coding density is about 660 bytes per frame.

Someone downloading at 3-4KB/s might find the download annoying; on the other hand, it could play back on a 66 MHz 486 DX they found in the dumpster. Good luck bringing up a modern browser with Javascript.

4KB/s second would be enough to get it to average around 6FPS in the first pass through the animation, if it were being rendered as it is being downloaded.

> it could play back on a 66 MHz 486 DX they found in the dumpster.

ooh actually, I got banned from the Ars-Technica forums way back in the day for posting a giant 800x600 animated GIF. It took browsers a few minutes to render! People thought I had crashed their browsers, but they just needed to be patient. ;)

I think I had a Duron 700mhz back then, so a 486 would've been turned into toast.

(Or, possibly, IE's GIF code was just really bad at that time! :) )

You're clearly more careful with resource consumption than many, then. Many times I've had enough time to start coffee while waiting for a .gif to get through its first pass so I can usefully watch it, using rural satellite from Hughes. We're still using your stuff out here.

Was it a TTY animation with most frames changing very few pixels (like one character cell), often with quiescent periods when nothing changes due to nobody typing?

Gifs can do silly things, like store a sequence of full frames.

Hughes is latency-bound, not bandwidth bound. For it to take that long, they’d have to try really hard. For instance, the map could issue an http[s] request per frame.

You can’t pause and rewind or skip ahead in a gif in any of the major browsers.

Or copy/paste text.

If I had users who needed to be that engaged with the content, they would be the sort of target audience I could easily have run scriptreplay in a terminal.

(I see this more as a demo thing to show people what is possible than, say, documentation to crib from, but I can see the value in being able to do that.)

FWIW, iOS 15 automatically OCR’s screenshots, so now copy paste works again (even from non-selectable text in apps).

However, most times I have problems with copy paste, it’s not due to gif’s. It’s due to some BS framework thing rendering text in some non-standard way.

Or render it beautifully for every future screen resolution.

9 times out 10 if you need something to be shown in video it's better to use an mp4 and screen recorder rather than stick it in a gif. It's usually smaller too.

> no third party websites

FYI you're not forced to use asciinema.org for hosting the recordings, it's fully self-hosting friendly: https://github.com/asciinema/asciinema-player/#quick-start

I don't want to engage in anything by the name of "hosting" to convey an animation.

Last time I made an animation, like this, over a year ago, I just pasted it into a team Slack channel.

Is there some tool like this that you could recommend? I tried searching for one at one time for my project, assuming someone must have written something like that for the script command, but didn't seem to be able to find a reasonable one (or any? I don't recall). I ended up writing an ad hoc single use tool for gif-izing an asciinema transcript... (https://github.com/akavel/asciinema2gif)

If you want to go that way, prefer html5 videos, they are a lot more compact and have options to pause and navigate.

I have no aversion to JavaScript. It's easier just to fetch the session as text and render it in the browser with JavaScript.

If you're building gifs then you need additional file storage and an async job queue for generating the gif. I try to avoid image processing personally.

As an enthusiastic Clojure/Script user, the new tech stack absolutely makes sense for this application.

For the decrease in size, I expect most of the gain to come from dropping ClojureScript. For the speed increase, though, I expect most of the gain to come from WASM. JS and ClojureScript are within the same margin of error compared to the performance that can be achieved with WASM.

> Due to ClojureScript’s immutable data structures, there’s a lot of objects created and garbage collected all the time

^ From the article, sounds like a plausible cause for the speed difference.

It's almost certainly part of it. I doubt that a ClojureScript application written mutably (which you can do) would compare favorably to WASM regardless.

Before attempting JS/Rust rewrite I tried using transients [0]. I converted small part of the vt code to use mutable data structures to see if it's giving any improvements. It barely did, the difference was negligible. I believe it was because I didn't go all the way, so the perf critical pieces deep down were still using immutable data structures. Maybe it would bring decent improvement if I converted most of the vt code to transients, but it would absolutely destroy the idiomatic aspect of the code, and even then it would never compete with Rust anyway.

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

I get that. And then you'd have to find a way to get rid of React (and Reagent/whatever was used on top of it) to get it all the way to as lean as possible. At this point, you'd be leveraging almost nothing that ClojureScript brings to the table, while getting most of the cons.

Spot on.

This makes me worry less about the end of Mores law. It's clear that software development still has a long way to go.

Reading through the comments and as an assembly programmer, I think this whole topic is completely bonkers.


For my own realtime browser based ASCII projects I update the DOM “manually”: the biggest bottleneck is frequent -horizontal- changes in color as each character with a new color needs to be wrapped in its own <span> element. After all you can’t avoid a DOM repaint.

After several benchmarks I realized that the fastest way to update the entire window is to compose a string and assign it to each line/line-element via innerHTML. I usually get 60fps with 5-8k chars in fullscreen (browser text rendering has become really fast).

Maybe you could benchmark it against this. It would make an interest HN followup post.

It's not the rendering / DOM part that got 50x faster though, so that would be apples to oranges.

Maybe it’s just me, but I don’t think showing a string on screen requires React or any other view library.

I’d probably paint it on canvas and then overlay an invisible plaintext node to allow selection.

The player has other UI parts, mainly the control bar with current time, progress/seek bar, full-screen toggle etc. Of course it could be built with plain JS but a small library like Solid.js does the job well.

The terminal lines with (colored) text definitely don't need a view library if you just want to display it, true. Canvas would be way more efficient here, and it's not out of the question for the terminal part in the future. One thing that using DOM (spans here) gives for free is copy-paste. Like you mentioned it could be solved by overlaying a text element on top of canvas (on mousedown, or when paused) or custom implementing copy-paste for canvas with mousedown/mousemove/mouseup, but that's all extra work, and as I mentioned in the blog terminal emulation was the bottleneck, not rendering.

But that just means you have to manage both the text (what React/SolidJS are already doing) and a canvas, which means your twice the work for no good reason.

If you made a library that does this I'm sure it would be welcome competition!

Yeah but how can you quickly re-render the plaintext node in sync with the canvas? How are you doing font rendering? How are you handling things like accessibility and interactive controls?

I feel like you may not be seeing the whole picture / problem domain.

People work with what they know and I'm sure they'd love to see a similar implementation done with the method you suggest. It's open source and hugs and thumbs up all around here :)

The blog post mentions that seeking to any point works very well without having to create keyframes, just from the speed of the implementation. It would be great if the progress bar could actually respond to click-and-drag events, to quickly find a specific location.

Scrubbing would be fun, I agree!

Asciinema server is still quite frankly my favorite Elixir Phoenix project of all time.


Oh wow, thank you! Frankly I'm the least proud of it, as it's been my first Elixir/Phoenix project and there's many things I'd have written differently today, especially after spending last 4 years writing Elixir at work. But time will hopefuly come to bring more love to it too :)

Reposting here since the first submission didn't make it to the frontpage

I started using asciinema two months ago and I must say that it's excellent! One minor annoyance though, it forces the use of the default shell instead of using the shell you launched it in. Other than that I am very excited by this release, more speed is always welcome.

It does try to launch the same shell as your current one by looking at SHELL env variable, and only falls back to sh if it's not there. See: https://github.com/asciinema/asciinema/blob/9ccf4efd4d3babc4... It seems that's not the most reliable method.

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


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

I get it, Clojure bad, Rust good. Yeah, absolutely nothing to do with the brain in the chair.

More like, know when to apply Clojure and when to apply Rust.

Having written a ClojureScript app with a Rust/WASM component, for me it's very clear that they have different strengths that can be complementary.

That the author dropped CLJS along with React is fair enough, in my opinion. The CLJS ecosystem is pretty React-centric.

Listen, Clojure isn't born yesterday, it's around for quite some time and it's a mature product. And when you have mature products that are still in development and people behind it do evolve it then you also get all kind of optimizations.

I would expect the system maybe to be slower like 20 to maximum 50% (as in Rust to be maximum 2 times faster) but 50 times faster?. That's already brain in the chair mistake.

If we were talking about Clojure, I would agree that 50x would indicate that something is off. But since we're talking ClojureSCRIPT, it doesn't surprise me.

Although, fair enough, it's a rewrite, and the author has the benefit of experience with the first implementation when writing this one. A second implementation in ClojureScript would likely have been faster than the first one. But 50x faster? Unlikely.

> Is WebAssembly faster than Javascript?

> Yes - about 50 times faster in my experience.


Just as a data point that the author isn't the only one to see this type of performance increase.

I expect that it isn't 50x across the board over JS, but that it would vary wildly across the board, including being slower if you're doing something trivial and end up paying more in serialisation to and from WASM than you gain in speed.

> But 50x faster? Unlikely.

And yet it is.

The biggest factor here is that CLJS is interpreted, GC-ed with immutable data structures while Rust is lower level with highly optimized WASM bytecode, that's optimized by Rust/LLVM compiler ahead of time.

So, when there's a lot of terminal activity (high speed colorful animation), the terminal emulator in CLJS implementation allocates and GCs millions of data structures every second. In Rust implementation there's very little allocation because most code operates on already pre-allocated buffers, and just mutates then. Both approaches are standard to their language. I could have tried all sorts of tricks (in fact I tried some) in CLJS to make this implementation faster but it would very quickly make the code non-idiomatic, and not fun to work with. But let's say I could make the CLJS impl be on par with a theoretical plain JS impl - this would likely still be many times slower than a basic Rust impl. Modern JS engines are amazing but they just can't beat WASM. Maybe some day :)

Read my comment again, I'm not saying what you think I'm saying.

Went to https://asciinema.org/ and clicked the demo video. I'm a bit confused because it looks like future lines are being rendered after the cursor while lines are being typed. Is that...correct? It looks like either a blatant bug in their product (that they're displaying on their homescreen demo!) or on purpose for some reason I can't find.

You made me click through, and I could not recreate. Is this the new viral marketing? I truly just was reverse psychology'd

not a plant I swear!

Feel free to report a bug here https://github.com/asciinema/asciinema-player/issues/new (including browser version, OS would help a lot).

Btw, it's not a product, just a side, hobby project of mine.

Ohhh weird, I thought it was a literal mp4 on the front page haha. Must be one of my browser extensions messing with it!

Ohhh this makes a lot of sense to me now too, I also thought it was an mp4 until reading your comment and thought you were totally insane to have "watched the video" wrong lol. I see now it's an animated terminal rendering via HTML elements, very cool

Applications are open for YC Winter 2024

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