Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
A vision of a multi-threaded Emacs (coredumped.dev)
172 points by pxc on May 30, 2022 | hide | past | favorite | 132 comments


One big challenge for multi-threaded Emacs is that there are many elisp libraries and common patterns around buffer modification that make implicit assumptions about sequential and exclusive execution.

The various race conditions that would appear in a multithreaded context would be a nightmare to debug because they would likely only occur sporadically and would be hard to reproduce. I would love to be wrong about this, but I see it as a major obstacle that will likely need to be sandboxed (as was done with lexical scoping).

For an example of how hard these kinds of issues could be to debug look no further than the incredible difficult that the undo-tree author had in debugging a race condition with the garbage collector. It took years.

In the context of this piece, having a buffer mutex makes certain use cases for multi-threading impossible, for example dividing the buffer up into chunk equal to the number of cores and running a regexp over each chunk.


Couldn't you implement things such that any number of readers can access a buffer concurrently, but modification requires exclusive access?


queues of compare-and-swap microtransactions


> One big challenge for multi-threaded Emacs is that there are many elisp libraries and common patterns around buffer modification that make implicit assumptions about sequential and exclusive execution.

It might be naive of me, but why not limit the process execution to the state and buffer that its working in ?

I know that this does not answer the multi processor per buffer problem, but its a better start.


I'd love to see this someday, as an Emacs user. One of my biggest frustrations with Emacs is how it can simply just hang while waiting for anything IO bound (like a response from LSP). It mostly only happens when opening a new file, while in VSCode for example the file is opened immediately and things like LSP and syntax highlighting are loaded in the background.


That multi-threading will solve this issue, is a common misconception. Emacs already supports asynchronous I/O and has sentinels for exactly that. However, not everything that runs on Emacs is taking advantage of that functionality. It is therefore up to you, to use the proper package (or not use the wrong one) / configure Emacs in such a way, so that this sort of issue is avoided. I can definitely say that if you're experiencing I/O hangs, it's either misconfiguration or bad code in a package that you loaded.

Finally for LSP, I always recommend Eglot over LSP Mode, as Eglot is written by an Emacs contributor that has deep knowledge of Emacs and Emacs Lisp. LSP Mode is written by volunteers on Github, and the code is nowhere near as good.

Given the (enormous amount / ever-increasing number) of Emacs Lisp packages out there, a lot of them being substandard, being able to make these sort of qualitative calls (and also increasingly dive into Emacs internals) will pay off. Assuming moderate effort, this will come with experience and time spent using Emacs.


This is putting a lot of blame on the user, who likely has no clue how emacs i/o behaves.

> if you're experiencing I/O hangs, it's either misconfiguration or bad code in a package that you loaded

I mean...yes. That's the issue. The issue is that it's possible to have i/o hangs caused by misconfiguration or elisp packages.

The solution isn't to tell the user to do something different, it's to make it so that elisp execution can't cause i/o hangs.

edit: to a be a bit more precise with language: "i/o hang" should be interpreted as "making the UI unresponsive or preventing other tasks from completing".


> This is putting a lot of blame on the user

I think you're missing the point. Emacs already contains all the support necessary to make LSP not lock up the UI, LSP simply doesn't use it. So if you say "LSP is a great use case for multi-threaded Emacs", that's not correct.

The problem with LSP specifically is that Emacs doesn't really know what the language server is going to do, and if it let you update the text while the language server was also updating the text, it would have to have some way to merge the changes that you made while the language server was busy. There would need to be some spec for that, or more likely, another trip to the language server to figure out what to do. You could, of course, edit the text while that second trip was happening, and this algorithm never converges to a finished state. So, it locks the UI so you can't make any edits until the text from the language server comes back. That's the easy way out, but at least it results in an algorithm that terminates.

The way to fix this is to figure out where the time is being spent. If it's on JSON marshaling/unmarshaling, speed that up. If it's because the language server is slow, speed that up. The only compromise right now would be to let you edit buffers in a different project while the language server is processing something. I don't think anyone wants to do that; "I don't want LSP to lock up Emacs" really means "I want my language server to perform all operations instantly." So get out that profiler for your language server, I think.


> I think you're missing the point.

I don't. "The only thing that matters in software is the experience of the user." The Emacs user experience is full of I/O hangs; the VSCode experience is not. This is a problem that needs to be solved if Emacs is ever to have a significant installed base again.


> The Emacs user experience is full of I/O hangs

I can't remember the last time I had an I/O hang in Emacs. I'm also fairly active in the mailing list and various forums, I/O hangs are not really what's being discussed day to day.

So I have to ask, are you projecting or what makes you say something like that?


As a third party in this conversation, and someone who uses emacs daily - I/O hangs seem to be something that is a pretty frequent issue for users.

My emacs likes to crash sometimes, usually a few times a week. Hopefully I’ll be able to take some time to bisect my config and see what’s going on. Looks like a memory leak.


To add to this, my Emacs never crashes but at least once a week I'll run an innocent command (sometimes just opening magit) and it will become unresponsive indefinitely, with the only way of closing it being sending a kill command from the terminal.


If your emacs freezes, you can often recover it by sending the USR2 signal. When it receives this signal, Emacs will drop in the debugger.


Sadly it's common with Gnus.


If this is the core of the problem, how does VSCode solve it? It seems users report less latency issues with VSCode - have they figured out how to handle the two-way communication better?


For the purposes of this argument I went and installed VSCode. I cloned kubernetes/kubernetes, opened a random file, added fmt.Println("hello") at the wrong indentation level somewhere, and pressed save. While gopls is initializing, it pops up a thing saying "waiting for code actions from the language server" (or similar; it wants the language server to "gofmt" the file). After that completes, the line I wrote appears at the right indentation level in the editor, but what's on disk is `fmt.Println("h` at the wrong indentation level.

Emacs behaves in a different but still incorrect way. It writes the entire fmt.Println statement at the wrong indentation level, locks up for a while, and decides that there was no formatting to do. VS Code will be fast after a while, but Emacs never gets fast; every reformat request locks up the UI for several seconds and doesn't actually reformat. VS Code remains responsive, but it's difficult to get the reformatted text on to disk. (Sometimes it does it, though.)

My main conclusion is that you're screwed if you have to work on k8s day to day. But on a more serious note, this isn't really an issue that can be solved with a UX tweak; the class of problem is "the optimal algorithm for providing instant hints on a codebase that is 214k lines hasn't been discovered yet". Yes, editors can let you mutate the text while it's working on something in the background; and if you turn of language server support that's fine. The key problem is merging your edits with its set of assumptions, and that's what's going to be slow.


How would you make it impossible to make the UI unresponsive? Given that lots of packages directly want to do stuff with UI?


I'm not going to have a great answer for how, especially in something with as much legacy as emacs. And I certainly don't mean to imply that solving the problem is easy (or possible for 100% of cases), or otherwise detract from efforts that the community has made.

My intent was to highlight on what a good solution looks like from a user perspective.

Off the top of my head, in an incredibly hand-wavey fashion:

A quick win would be to set a timeout on every single i/o action, with the default timeout being quite low. That doesn't remove the problem, but it'd probably be a good start towards easing the user pain. Bringing an indefinitely long hang down to 1s is a win.

For a total solution, I'd consider looking into implementing a concurrency model with a preemptive scheduler, probably using erlang as inspiration. have updates to the ui becoming a message sent to the ui inbox, and the routine for rendering the ui just reading from the inbox and applying the given command.

This is without thought to how to actually implement that, and what implications it would have on legacy elisp, or how legacy elisp could be brought into a CSP world without breakage.

So just some armchair architecting on a day off for a project I've never worked on :)


I've seen you post this criticism before, but the line "volunteers on GitHub" is pure FUD. Is the Eglot guy paid for writing Eglot, or he hosts somewhere else, so it's better?

"The code is nowhere near as good" according to you? Give a concrete example of where the LSP code has real deficiencies; Eglot isn't as fully featured, is more opinionated, and its stans are more annoying.

Sounds to me like the LSP project is helping new hackers learn Emacs, and Eglot is one bus accident from being unmaintained, frankly.


Yup, I have seen this person post something similar at least once before as well and it's pure misinformation at best.

Both projects are very good and everyone should give both a shot and see which they prefer, but spreading misinformation about one project because you prefer another is stupid and childish.

A quick peek at the github contributors insights also shows that the overwhelming majority of code is contributed by 3 people and not "volunteers on github", but even if it was mostly worked on by volunteers it would make no difference at all.

Code quality is not something that can be trivially disproved like the volunteer thing, but as someone who has spent a bit of time poking around in both projects, neither look any better code quality wise to me. The main thing is that lsp-mode is just much, much larger and prefers to use non-native APIs and packages at times. This is purely a difference in style and opinion rather than "quality".


>Finally for LSP, I always recommend Eglot over LSP Mode, as Eglot is written by an Emacs contributor that has deep knowledge of Emacs and Emacs Lisp. LSP Mode is written by volunteers on Github, and the code is nowhere near as good.

This isn't really true at all, but even if it was, lsp-mode has a larger scope and has many more features than eglot. It's also much more actively maintained/developed than eglot is.

Anyways, most FOSS code is written by volunteers. I really don't think that's a great argument against a FOSS project.

Also see:

https://github.com/emacs-lsp/lsp-mode/graphs/contributors

https://github.com/emacs-lsp/lsp-mode/pulse/monthly

https://github.com/joaotavora/eglot/pulse/monthly


> I can definitely say that if you're experiencing I/O hangs, it's either misconfiguration or bad code in a package that you loaded.

As an emacs user and package developer of 20 years I can definitely say you are totally wrong. This is the kind of dismissive, unhelpful, and frankly extremely discouraging attitude that is slowly killing emacs.

Instead of taking responsibility for the fact that emacs is incredibly slow because of poor architectural decisions (like an insistence to not using fast native libraries and an incredibly slow language), the solution is apparently to blame the people who create packages. If developers they are creating packages that are slow, it is not their fault. It is the fault of emacs developers in refusing to modernize the architecture in a way that enables people to make efficient code.


The presupposition that anything is killing Emacs - much less this - requires concrete proof. Because if anything, in my (likewise subjective) experience it's the complete opposite. I see more forum activity from newcomers and more high quality packages emerging each year than ever before.

> like an insistence to not using fast native libraries

How? Even disregarding graphics and font stack which almost entirely leverages native libraries, my Emacs build links against gmp, libpng, librsvg, zlib, alsa-lib, libxml2 and so on. 27.1 introduced jansson for native json parsing over pure elisp, from 28.1 we can basically AoT compile elisp to native code using libgccjit, master branch (29) even bundles sqlite these days. That is not to mention module system had been stable for a while, and lot of packages already use this to offload computation heavy stuff to native code.


>I see more forum activity from newcomers

It terrifies me how often new users today are gasping for breath on the shores, ungainly four limbed tetrapods gasping for breath...this isn't progress and it's not evolution, there's so much more of everything but it's not at all higher quality for novice users than I grew up on forty years ago.

> more high quality packages emerging each year than ever before

I question my own ability to quantify modern software quality. Compared with what? My working lifetime has been dominated by corporate shovelware and that's absorbed multiple generations of misguided human and fiscal wealth creating structural deficit immeasurable compared to the Cambrian bounty of my early years. Edited last sentence for clarity.


> This is the kind of dismissive, unhelpful, and frankly extremely discouraging attitude that is slowly killing emacs.

Emacs seems to be doing just fine if not thriving.

> Instead of taking responsibility for the fact that emacs is incredibly slow because of poor architectural decisions (like an insistence to not using fast native libraries and an incredibly slow language)

For me, Emacs is both incredibly fast and incredibly stable. For an "Emacs user and package developer of 20 years", you seem either too quick to jump to conclusions or are still barely scratching the surface of what's possible which makes me question the depth of your expertise and/or how efficiently your time with Emacs was invested.


The worst non-package related offender is long lines imho. I think opening a file or buffer with a long line is the only immediately obvious hanging I see running plain emacs. I know there are a lot of technical difficulties, but fixing this issue would be a nice UX improvement.


If your lsp-client code is doing anything where you need binding with a native library or the speed of emacs lisp is a factor, you are doing it wrong. All an lsp-client needs to do is make requests to the lsp-server, listen to the responses and update the UI. It literally doesn't matter how slow emacs-lisp is.

The only way an lsp-client could be slow is by making a request to the server and then blocking on the response - which is wrong and the package writer's fault because they are not using the asynchronous api emacs provides for communicating with a process.


There's tons of little details and inefficiencies in elisp that crop up such that easy things like "listen to responses" are hard to get fast. Random example: before native json support, large lsp responses (which can be multiple megabytes from some servers!) would take over a second and hitch the UI just to parse the json response. And that was after a lot of work to get response parsing dominated by json instead of GC time.


> and an incredibly slow language

Languages are not slow or fast; their implementations are. There's absolutely nothing wrong with Emacs being programmed in Lisp and it sure as hell isn't "a poor architectural decision". If Emacs is slow because of its implementation of Emacs Lisp, it's time to fix its implementation of Emacs Lisp.


> Languages are not slow or fast; their implementations are.

Right, but languages have finite extant implementations. If a language has no fast implementations then it's reasonable to call it slow.


> There's absolutely nothing wrong with Emacs being programmed in Lisp and it sure as hell isn't "a poor architectural decision"

I love Scheme and Lisp. I've probably written several million lines of code in both Scheme and Lisp and I've contributed code to several Lisp and Scheme compilers.

But languages are absolutely slow or fast! There are fast Schemes and Lisps and there are slow ones. Emacs has probably the slowest one by far. For example, Emacs still relies on dynamic binding after all of this time. That's a performance disaster.

There is simply no efficient implementation of Elisp possible as it stands and as it is used today. Even with JIT compiling Elisp performance is simply wretched.


> I've probably written several million lines of code in both Scheme and Lisp

BTW you do realize that this would put you at (sustained, average) several hundred lines of code per day (every day) over your lifetime? That sounds almost depressingly bleak to me. How does one have a life outside of that?


> BTW you do realize that this would put you at (sustained, average) several hundred lines of code per day (every day) over your lifetime? That sounds almost depressingly bleak to me. How does one have a life outside of that?

Grad school sure puts you on the right track of getting good numbers like this! :) And the answer was, not having a life outside of grad school.


Grad school doesn't last forty years though...or does it?


I should hope not!

Also, 40 years? 1000000/40/52 ~> You're assuming the average developer writes 500 lines of code per week? That seems absurdly slow.


Well, you did say "several millions", so that would be more like 2000 lines. And depending on the environment, I'm aware of many people who do much less (not of their own volition or choice), so I have little faith in a long-term average being this high. I'll have to take a look that what Capers Jones wrote on this...


I'm lucky if I write 500 lines of code a quarter these days....


Then the codebase needs to fully (or as much as feasible) transition to the use of lexical bindings. It's not like we don't know if it will work; Common Lisp supports dynamic variables but you don't pay for them unless you actually use them, and good implementations of CL are fast enough. So what exactly is it that prevents the Emacs Lisp code base from becoming fast enough by abandoning dynamic variables wherever it can? By all rights it should not be any slower than Common Lisp at that point, unless some other obstacle has somehow eluded my attention.


> But languages are absolutely slow or fast! There are fast Schemes and Lisps and there are slow ones.

You are contradicting yourself. If languages instead of implementations were slow or fast, then their would not be fast and slow Schemes and Lisps. Scheme would be either slow or fast.

Actually, I would go even further than saying only implementations are slow or fast, they are slow or fast for certain use cases.

> There is simply no efficient implementation of Elisp possible as it stands and as it is used today. Even with JIT compiling Elisp performance is simply wretched.

I agree in so far as there will --- probably --- never be an Elisp implementation which can run numeric intensive code as fast as the best Fortran or C compiler for this task. But it is certainly possible to improve the performance of ELisp even more than native-comp branch did. The question is whether there are people capable and willing to invest the time to develop that and whether spending that time on the language implementation is the most useful way to spend that time.


> You are contradicting yourself. If languages instead of implementations were slow or fast, then their would not be fast and slow Schemes and Lisps. Scheme would be either slow or fast.

You are unfamiliar with the huge differences between what counts as a "Scheme" or a "Lisp". Neither "Scheme" or "Lisp" are a language, they're language families. The difference between Schemes is larger than the difference between say C and Ocaml, or Haskell and Javascript.

Depending on what features a particular Scheme or Lisp has, it can range from insanely fast (like, faster than an optimizing C compiler), to mediocre (like Python), to dirt slow, like current Elisp. It all depends on what the language has, and how it encourages you to write code. For example, having a type system tends to make your language very fast.

> Actually, I would go even further than saying only implementations are slow or fast, they are slow or fast for certain use cases.

Compilers are not magic. And compiler technology has not fundamentally advanced for decades now. There are basic limits to what a compiler can do. Some language features simply destroy performance, and the lack of some language features prevent compilers from getting good performance. And then, yes, there is also style. For example, relying heavily on language features that are inherently slow, will do you no favors.

Elisp is the pinnacle of a terrible language used in a terrible way. It lacks all features that make languages fast. It has plenty of features to defeat optimizations and preclude having any fast implementations. And people write Elisp in a gory style that makes everything worse.

> I agree in so far as there will --- probably --- never be an Elisp implementation which can run numeric intensive code as fast as the best Fortran or C compiler for this task. But it is certainly possible to improve the performance of ELisp even more than native-comp branch did. The question is whether there are people capable and willing to invest the time to develop that and whether spending that time on the language implementation is the most useful way to spend that time.

I don't care about numerical code. I care about the fact that emacs is dirt slow.

It's been many decades now. Elisp performance has barely budged. Even the native branch only marginally improved things in some edges cases. There is zero evidence that more effort for faster implementations will go anywhere.

In general. More developer time doesn't meaningfully speed up a language even on the timespan of decades; this means like even say a huge effort, a millenium worth of hours of developer time (100 developers full time for 10 years), makes no serious difference to the performance of the average app running on the JVM (a few percent), it mostly just patches edge cases. It's the same in Haskell. GHC 7, from 10 years ago, is marginally slower than GHC 9.4 if you run the same large app in both.

What makes a difference to performance is changing the language. Adding language features that the compiler can understand (that's why say, Haskell code is far faster today than it was 10 years ago, it's a different language, with different features, that we write in a different way).


> GHC 7, from 10 years ago, is marginally slower than GHC 9.4 if you run the same large app in both.

I don't have access to $someLargeApp you mrntion, but I find this hard to believe.


As mostly a heavy user, most of the time I find emacs quite snappy.

Where do you find your package code most throttled? Do you have a top-3 wishlist you would want the core team to put in?


While I appreciate the comments on Eglot vs LSP, the number one package that hangs my system is Gnus. It's still my favourite e-mail client, but I'm cut off from coding for twenty seconds every time I check my e-mail.

Are we really ready to declare that Lars Magne Ingebrigtsen is a substandard Emacs Lisp coder?


Gnus has been around since the mid 90s (and predates the addition of asynchronous url-retrieve api to emacs). It is unfair to compare it with packages that written in the last couple of years.

I don't use Gnus so I have never bothered looking at its internals, but if it is locking your emacs for twenty seconds checking email, it is probably not doing much asynchronously. It is notoriously hard to convert existing synchronous code to asynchronous without using threads. Promises/futures/continuations/whatever-you-want-to-call-them are a pretty all in affair. Not having the time investment that would take to convert a 100k line codebase to async does not make Lars Magne Ingebrigtsen a substandard Emacs Lisp coder.


This. Gnus is a behemoth, has been around for decades and is also the result of many contributions from many different people. It will take time for it to evolve (it's slowly happening).

Lars is amazing by the way, his relentless Emacs work and prolific bug killing in the last few years should be praised to high heavens.


Not only that but he is incredibly responsive to random people (including me in the past) reporting bugs in Gnus. Years ago I reported something on a Friday morning and he replied directly to me with a workaround by the afternoon.


I use mu4e so I don't know if the workflow converts but I watch for new email outside of Emacs entirely and upon new email run offlineimap and after sync have it run mu4e-update-index via emacsclient.

This means Emacs always has an up to date view of my email within a ~30 second window of the email being received and all I'm asking it to do is query mu which takes ms.


For me the most frequent source of freezes was Tramp. It led me to ditch Emacs for VSCode (with the remote editing extension) at least for my dayjob (I still use Emacs for my own projects). Tramp is an official built-in package, so it's not as if I downloaded a random package from GitHub.


While I did not ditch Emacs nor do I plan to do so, I agree, that TRAMP is a constant source of grief. It is great in its functionality, but frequent freezing is annoyingly cold.


These issues are often interactions between packages. I use TRAMP daily without freezing, but only when I disable company mode in shell modes. VSCode certainly requires much less of this tweaking, even with a variety of extensions installed.


Another vouch for Eglot here. Seems smoother and just nicer overall compared to lsp-mode, not to mention better integration with Emacs. I switched to Eglot a while back and haven’t looked back.


Joining the eglot choir. It behaves like I expect Emacs modes to behave and hooks into other Emacs functionality (xref, eldoc). Actually, it behaves better than that: it often works with zero configuration, which is much better than I expect.


Glad to see this as the top response here. I was rushing to click reply so that I could point out that async has been a thing for Emacs for a long long time. Compilation mode is my goto example for how that can work.


> It is therefore up to you, to use the proper package

This is the single biggest reason I don't use emacs. I have yet to get a stable installation with the necessary behaviors and tools I need to make memorizing all the chords worth the effort.

I've tried spacemacs, doom and from scratch, and have never been satisfied or gotten to the point where coding is as productive as VScode for me.


As an innocent user I tell you that LSP mode just works whereas Eglot needs tailoring to be a joy to use.


Hm, strange. As I remember, eglot’s design goal was zero configuration. Could you elaborate a bit? For context, I use go, had gopls installed and just had to start eglot. All xref shortcuts worked immediately.


Lsp comes with preconfigured flycheck for example.


I just switched to eglot from lsp-mode, flymake worked by default.


I just got into emacs last year, for Org mode. Startup times drive me crazy.

What would be the correct way to configure my .emacs file to e.g. check for package updates in a background process?


Use emacsclient and have a emacs server running at all times. Super fast startup times, you can even use emacsclient -nw (or make an alias 'e' for that) in the terminal for fast edits with instantaneous startup.

You could also make a script that starts emacs as a client only if a server already is running

  #!/bin/sh
  if [ "$#" -eq 0 ]
  then
    echo "Starting new Emacs process ..." >&2
    nohup emacs > /dev/null 2>&1 &
  elif emacsclient -n "$@" 2> /dev/null
  then
    echo "Opened $@ in Emacs server" >&2
  else
    echo "Opening $@ in a new Emacs process ..." >&2
    nohup emacs "$@" > /dev/null 2>&1 &
  fi
(Copied from an emacs starter kit but don't remember which...)


You don't need to do all that. Just run emacsclient with `emacsclient -a ""`.

From the man page:

    -a, --alternate-editor=COMMAND
           If  the  Emacs  server  is  not  running, run the specified shell command instead.  This can also be specified via the ALTERNATE_EDITOR environment variable.  If the value of ALTERNATE_EDITOR is the empty string, run
           "emacs --daemon" to start Emacs in daemon mode, and try to connect to it.


Didn't know that, thanks!


This is really “just leave Emacs running all the time,” which is more of a workaround or an acceptance of “fit your workflow around Emacs” than it is truly a solution, which would be to reduce startup times.


Once it starts it does almost nothing unless you're using it, and then it's still a lightweight document editor, not an Electron app


Why do you quit though? I don’t quit other programs... why is Emacs different?


I quit and restart Vim dozens of times daily.

I’m not saying there’s something wrong with leaving Emacs running. But it’s a workaround. When I used Emacs, startup time was not a problem. But I didn’t use a bunch of packages.


Emacs is not supposed to be used like vim. The stereotype of living inside emacs doesnt exist for no reason.

Apart from that, without too many paclages stsrtup time is not an issue imo


I never used to quit vim either!


A no-config Emacs has a rapid startup time - it's instant on my PC.

> or an acceptance of “fit your workflow around Emacs” than it is truly a solution, which would be to reduce startup times.

I don't see a problem with this. Emacs is a general purpose platform, and it's silly to expect really fast startup times if you're using it as a general purpose platform. People aren't expecting Linux to have an instant startup time if you have a large number of services running - why expect it from Emacs?


Actually, reducing OS startup time used to be a goal. I think that stock Ubuntu on run-of-the-mill hardware (spinning rust) was down to 14 seconds bios-to-login at some point when they made an effort on that front.


And stock Emacs load time is virtually instant.


Acceptable workaround, though. I don't mind when people XY analyze my problems, often those with experience in a field will be mindful of details that I did not know.

For what its worth, I've got at least 23 (service --status-all) daemons already running on this desktop. So leaving things running in the background until they are needed has precedent.


My fresh emacs startup time is a couple of seconds, not super snappy but then it has a reasonably large config to load. Reducing startup time is nice but I think vanilla emacs actually already starts quickly. How would you even be able to prevent people from adding heavy stuff to their config? It's possible to defer loading of packages until you need them but I think it would be hard to enforce by default, given that Emacs is really just a lisp machine and the config file is a lisp program.

I normally never exit Emacs. This is not because of startup time. To me it's like the web browser; I use it all the time, have a workspace dedicated to it and it's natural to keep lots of buffers open. My muscle memory takes care of jumping between open buffers.


If a process isn't using any resources, is it really running at all?

(Of course it doesn't really use no resources - it consumes a finite PID among other things)


Terrific, thank you.


Don't check for package updates during startup? Setup a cron job to upgrade the packages while you are asleep?


It seems that Emacs does the check automatically, no? I have looked briefly on how to disable that but I didn't have the prerequisite knowledge at the time to understand what was happening. I'll look at that again, thank you.


Maybe you're using (use-package foo :ensure t) or something similar? I did that before, and if there was some package that simply wasn't available it would of course check and not find anything every time I started emacs. I don't use :ensure t any more (I check the elpa folder into git along with my init file for keeping it in sync across machines)


>It seems that Emacs does the check automatically, no?

Nope, it for sure does not.


Using Doom emacs was the easiest way for me to get fast loading times. It provides some sane defaults that should be pretty fast in most cases. It might be worth checking out.


Thanks. I just left Spacemacs for vanilla. A distro was a great way into the Emacs ecosystem, but now I'd like to configure the thing to my use cases specifically.

I might look at the doom config and try to divine how they're getting such fast loading times. Thank you for the suggestion.


Don't do automatic package updates. It's a sure way to continually break things.


Why does Emacs startup time bother people so much? It's pretty quick for me and only happens once every few months anyway.


You should use `use-package` to manage your packages and defer their loading to when you use them. I have over 100 packages in my init.el and my Emacs loads in less than a second.


Thank you. I'm not using many packages, so I'm not sure what to change. Using csv for example:

  $ grep csv ~/.emacs
   '(package-selected-packages '(csv-mode org-evil dash)))
If I want evil all the time but csv only when needed, I should change that to the following?

   '(package-selected-packages '(org-evil dash)))
   '(use-package '(csv-mode)))


The package-selected-packages is just the list of installed packages, doesn't load anything.

You can set

    (setq use-package-always-defer t)
so anything you load with use-package will only be loaded on-demand. Or you can do it per package with e.g. `(use-package csv-mode :defer t)`.

Also make sure you don't set use-package-always-ensure and don't `:ensure t` or it'll try to install anything that's not installed on startup. If you want fast startup, it's better to explicitly install with M-x package-install


Always defer is a bad idea because some packages are required at startup or look for variables defined at startup (which is why `:init`) is a thing. Also, `:ensure t` only installs what is not installed at startup, but it does so only once and the following updates are to be made by hand. Once this is done, it does not try to install the packages anymore unless you remove them. This is how you get both fast startup and feature availability. Agreed on not using `always-ensure`, there are builtin packages that do not need it and will error out, this is why you should `:defer` and `:ensure` yourself.


You should have everything managed by use-package for coherence, even when you don't defer. For example,

(use-package org-evil :ensure t ;; download if not present :defer t ;; defer loading hook (org-mode . org-evil-mode)) ;; start this mode in org buffers

(use-package csv :ensure t :defer t) ;; modes are usually smart enough to know in which buffer they start

and so on for all your packages. If you NEED something to start when Emacs open, you can remove the `:defer t` macro. There are also other useful things like `:init` `:config` `custom`, you should check out the docs!


Emacs starts up in half a second or less for me. I use a vanilla configuration and don't update packages every time.



How often do you start Emacs? I do that maybe once a week.


I start Emacs every few minutes, for Org mode notes.


You do not have to start a new instance of Emacs every time. You can tell emacs to start a server and then use emacsclient to connect to your already running instance of emacs.


Thank you Alex. I'm configuring that now.


> Emacs already supports asynchronous I/O and has sentinels for exactly that. However, not everything that runs on Emacs is taking advantage of that functionality. It is therefore up to you, to use the proper package (or not use the wrong one)

>Finally for LSP, I always recommend Eglot over LSP Mode

As far as I can tell, Eglot also just blocks waiting for the server response.

Unfortunately, that is the current state of the emacs package ecosystem. Org mode freezes emacs. Magit freezes emacs. Most package writers just don't bother writing their packages with asynchronous I/O in mind. There is no point blaming the user.


Wow, I had no idea! I'll give Eglot a try, thank you :)


I use Emacs all day, every day at work for the last 20 years. I'd like to see these changes too, but honestly it's not even close to being on my top 10 of frequent frustrations. The average web app is way less responsive, suffers random hangs, HTTP 500 errors, browser freezes, etc. than I experience in Emacs.


You don't need multiple threads to perform IO asynchronously.

And in reverse, using threads won't prevent lock-ups: the opposite is likely. [Pre-emptive] threads compared to external processes require much more careful coordination that is hard to achieve if you are installing dozen of emacs packages written by different people that manipulate the same state.

unrelated, it is one of the arguments in favor of GIL such as in CPython--it is easier to get right than fine-grained locks.


Agreed. Raw threads are almost always the wrong application-level concurrency abstraction. Actors/Erlang-style concurrency and futures are both much better abstractions for application programmers. I'm also a big fan of threads with compile-time proofs against data races, a la Rust, but I can appreciate it's a lot to learn for many application programmers.


> ... while in VSCode for example the file is opened immediately and things like LSP and syntax highlighting are loaded in the background.

I'm not saying you're not experiencing what you're describing but I find that weird because I use Emacs with LSP and the system is responsive while LSP loads up. And I'm using lsp-mode, not Eglot (I should really try Eglot). I mostly use it for Clojure: I'm using both Cider and lsp-mode and they work fine together. And as soon as I open any Clojure source file, I can keep using Emacs while LSP starts (which is not that slow btw: only about three seconds I'd say). LSP is one of those things that runs in another process, so it's one of those case, for me at least, where Emacs acts like if it was multithreaded. I haven't seen this lock up / freeze while waiting for some LSP server answer.

For example I'll typically open a Cojure file, lsp-mode shall begin loading automatically and then I manually launch Cider: everything happens "simultaneously" and I'm not blocked.

I'll play more attention with other LSP servers (like the CSS one) but nothing strikes me as slow or frozen.

If there's one thing I could complain about is that when I'm busy doing other things (because my Emacs ain't blocked), I see spurious LSP messages telling me where it's at kinda distracting me.

Now I take you're using a native-comp Emacs? And a fast JSON parsing lib?

Emacs 28, native comp, lsp-mode, AMD 3700X, NVMe SSD, 16 GB of RAM. A good machine but nothing crazy fast.


I have seen some folks that don't realize they are bypassing the native-json parser by having debug logging turned on. So, could be that?


This kind of problem can be solved without threads. Single threaded programs can do things instead of waiting for I/O to complete.

In a Lisp you should have continuations and whatnot to be able to the event-driven programming for that without the inconvenience of having to express all the background tasks as transitions in a state machine.


My biggest pet-peeve is Org freezing when I'm exporting to PDF.


You can change it to export in the background:

  (setq org-export-in-background 't)
https://orgmode.org/manual/The-Export-Dispatcher.html


Thanks for the tip, I'll try it out!


I've had that fixed in the past by simply adding an ampersand to org's compile command. This does make it run in the background but makes it ring the bell a number of times. Now when I'm editing and want to see the compiled pdf side by side I just start a bash while loop that recompiles continually with latexmk.


> I just start a bash while loop that recompiles continually with latexmk.

There's the -pvc option in latexmk that[1], "continuously check all input files for changes and re-compile the whole thing if needed and always display the result," depending on if your PDF viewer will refresh.

That may obviate the need for a bash while loop.

[1] https://mg.readthedocs.io/latexmk.html


Neat, thanks!


That makes sense, I don't need to recompile that often but when recompiling relatively large notes (~50 pages) with lots of minted code blocks and the like, it's annoying.


Also large 100mb+ text files seems to bring scrolling to a crawl in Emacs.


Note: Not at all an emacs expert, but know some stuff about trying to parallelise a long-standing language ( GAP -- www.gap-system.org , 30 year old maths language).

I'd strongly recommend something like Javascript's "web workers", where each thread lives in an almost entirely independent memory space, with sharing limited to explicitly named buffers.

The problem with traditional "everything shared by default" threading is even when you get the basic core working, we found throughout the system there were buffers and caches, which all needed making thread-safe. Even after that, every library needed some fixes -- and while these were often simple these libraries may not have had updates in years.


In principle I agree whole heartedly. When introducing parallelism and or concurrency into an old language, the "web workers" aproach, or Erlangs actors, to name a decades older reference to a similar idea, should always be considered. The problem with emacs in particular is the use of the buffer as the most versatile data structure. Lot's of data that could live in some data structure lives in some overlay over some part of the buffer. And emacs is built on dynamic variables. Others in this thread have argued to "just refactor" them to lexically scoped variables. But that would decimate the largest vector to adapt and modify the system.

Parallelizing Emacs needs to be a long-term endaveour, as the maintainers of Emacs already said years ago. I think Emacs could learn something from Clojure's story for parallelism. Make data structures immutable by default, and reference them from as few global variables as possible. The "make data structures immutable by default" ship has already sailed, but introducing additional immutable list/vector/map/set data structures with first class syntax support would allow newly written code to prepare for a more parallel future. Such immutable data structures could then be shared between different "ELisp web workers". The "as few global variables story" ship has already sailed, but again ELisp authors would start to consider reducing the number of global variables if it would enable multi-threading.


Yeah, I think that's the way... Dart does this well with Isolates[1]. The language is basically single-threaded but you can start Isolates that run on different Threads but have their own independent memory, and they can exchange messages, much like in the Actor model. Not having to worry about real shared memory between Threads as in C++ or Java is incredibly liberating. As others point out, however, for extremely high performance that's not the best solution, but for nearly all applications, including emacs, the Isolate approach is plenty good enough.

[1] https://dart.dev/guides/language/concurrency


I'd argue that emacs already has that with process buffers using sentinels. :D

By and large, if you don't write blocking code, it doesn't block. Big caveat here is the TRAMP logic to establish connections can block. Not sure why those weren't done in a more async way.


This is my only real gripe, as well. I really like Tramp, so I use it anyway, but I gripe about it when it hangs


one really nasty conflating issue is that emacs uses a mess of globals. partially because of the domain but largely because of dynamic binding and the lack of namespaces.

emacs really needs to be hoisted onto another language and runtime. everyone says this but no one can really conceive of doing this and rewriting or at least refactoring all the elisp extensions.

I guess guile emacs is still alive? I should really try installing it


Guile-Emacs is no longer alive. The only other project similar since then has been remacs, but that aimed to replace the C core, not elisp. It also lost momentum. I believe adding parallelism to elisp will be more feasible and successful than another rewrite. It's not a bad language really.


I wonder if it would be possible to automatically rewrite the elisp code to whatever new runtime was chosen? Assuming the new language is still more-or-less a Lisp ... maybe?


The issue is always the parts where the two language semantics differ too much. And then, you usually have too much legacy code that would be impacted by changes in those areas. That's the hard part.

You lost when that code lies beyond some organizational boundary, e.g., a separate project that has is too big in its own right to refactor in one go, or where the maintainers have sufficient clout to resist such a change. Such a transformation would effectively entail a fork of that project.


guile-emacs has atrocious string performance


The author discusses essentially this option in "A jumping off point."


WebWorkers are not very useful as a result. The communication overhead of copying data between two memory spaces usually dwafs any apeedups you could get from doing the work in parallel.

It's easier to implement as a language and runtime, but to get truly high performance on today's many thread many core machines you need shar d memory concurrency more like Java


> to get truly high performance on today's many thread many core machines you need shared memory concurrency more like Java

JavaScript supports shared memory concurrency.

Shared memory¹ is available in browser WebWorkers these days.²

Atomic ops for building multi-threaded data structures³ are available too⁴.

Including primitives for thread synchronisation⁵⁶ used by mutexes, condvars, producer-consumer queues, etc.

¹ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

² https://caniuse.com/sharedarraybuffer

³ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

https://caniuse.com/mdn-javascript_builtins_atomics

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

https://caniuse.com/mdn-javascript_builtins_atomics_wait


Arrays of bytes are practically useless in Javascript. And the message passing system for NodeJS workers is almost as useless because it's just as alien to the rest of the javascript normal people write as shared byte arrays is. What we needed was message passing implemented as an async operation that awaited a response. Bidirectional emit() operations don't scale.

If you were building something on top of them using wasm then you probably wouldn't even know what the primitive was. But we're not. We're writing Javascript, and it's a damned shame.

When someone takes a really long time to make a really bad solution, there's frequently not enough group will to correct the issue. This will be the bullshit we are stuck with for the next ten years.


True, they are of limited usefulness, but in Emacs you could (probably) make them more useful over time by exposing more and more sharing -- but (I'm again guessing, don't know Emac's insides) it might be easier/safer to start sharing nothing, then work on sharing as much as possible, rather than start with full sharing and then try to make everything work.


One would have thought a purely functional piece of software written in Lisp was the poster child for parallel execution. Emacs clearly can’t easily do that, as per the article — the core of what it does is stateful — but what are some good examples where scaling horizontally with pure functions has worked?


emacs isn't pureful functional, and I don't think much lisp code ever was


Exactly, lisp was always multiparadigm, and only more recently clojure has pushed forward the idea that being as purely functional as possible (with immutable data structures and pure functions) is something that works pretty well in a lisp.


Note that Clojure offers multiple concurrency systems, the default STM is not particularly performant and doesn't scale very well. You are better off calling into Project Loom for concurrent code.


Clojure is memory safe and no data races possible?


Memory safe, perhaps, but data races are definitely possible. The main issue is that their STM system doesn't scale very well. It is good enough if you want to keep a simple CRUD GUI in sync but don't expect to be building high performance web servers anytime soon with Clojure atomics. You can't just create and throw those around willy-nilly like Goroutines. Clojure people doesn't like talking about the language's weaknesses but I have seen quite a few Clojure projects get rewritten because of maintenance cost and runtime inefficiencies. Project Loom is the best option right now for scalable Clojure concurrency/parallelism.


STM, atomics and channels are all different things that can be use to build different kinds of systems, so I think it's good to disambiguate them when talking about concurrency problems and their tradeoffs.

Clojure' STM is basically unused. It's a suite of functions and a container type `ref` that allows you to do transactions in memory and keep everything consistent within the view of a transaction. The overhead required to do the book keeping for these transactions is often not worth it for most use cases in practice.

Clojure also, as an alternative, gives you `atom` which is a type that implements a simple thread safe compare-and-swap. This ends up being sufficient for many cases of sharing state between threads. When combined with Clojure's immutable data structures, one can often use them as a simple in memory database.

Those two things are about sharing memory, but has nothing to do with what the article talks about: green threads.

clojure.core.async, as the article succinctly explains, allows you to create "goroutines" or green threads which are then multiplexed across some number of OS threads. This can allow you to create many more threads for computation than you have OS threads, and combined with thread locals and atoms can allow one to build large scale systems.

Clojure's STM and atomics are sort of a non-sequitur from what the article is talking about

Now as for downsides of Clojure, there are plenty of gotchas in practice that can lead people to issues with the three things above:

* STM is slow and rarely useful compared to regular atomics

* atoms CAS assumes you are using immutable data structures

* core.async has a lot of sharp edges, including lack of first-class error handling and the fact that doing synchronous I/O on a go routine can end up starving the thread pool they are running on

I've never built a high performance web server, but I imagine it's possible with Clojure; it would probably require you to probably eschew immutable data structures within the plumbing (for performance) and use an async I/O framework along with core.async to get competitive with a language like go. For shared memory, you probably would end up eschewing compare-and-swap for something more bare bones like locks, which you have access to via Java interop. At that point, you may wonder why you even chose Clojure in the first place instead of using Java directly. :D

Project Loom really only helps solve the problem that core.async is trying to solve, and applications would still make use of atomics like `atom` and basic Java atomic values. My hope is that it makes it easier to do I/O, since it sounds like along with loom fibers the project is adding first class support for suspending and resuming on fundamental I/O operations. Time will tell.




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

Search: