Difficulty migrating from vim due to performance

I’ve been having a lot of fun trying to migrate from Neovim to Kakoune over the past few weeks, but there is one recurring theme that’s been a surprising problem for me: performance.

I’m finding certain interactions quite a bit slower than Vim, which is jarring and counter to my intuitions since my Vim setup is more “fully loaded”. Here are some things I noticed that are noticeably slower:

Startup time
I’ve read through this thread, but even using Dash, I can’t get time kak -e "quit" below 310ms which is 50% slower than Vim for me. I forced myself to get over it and also started using a script that will reuse a session based on the current directory.

Fwiw I symlink to the system autoload directory and have a few files of my own in autoload, but not much. I also see that kak -n is way faster, but as soon as I have a kakrc, things slow down.

Opening files
Opening files has a noticeable latency compared to Vim. This started becoming really noticeable when I added a command that opens FZF in a Kitty overlay pane. When selecting a file, FZF will close and show the old buffer briefly before switching to the new one:

ScreenRecording2025-11-08at8.25.18AM-ezgif.com-video-to-gif-converter

Formatting
I use Ruff for formatting Python code, but invoking it in Kakoune using format_cmd has a slight delay. Using set global debug profile indicates that it’s taking ~120ms, but running the equivalent command in the shell takes 15ms:

shell execution took 110355 us (spawn: 945, wait: 108876)
command execute-keys took 111312 us
command nop took 1 us
command evaluate-commands took 111579 us
command format-selections took 119210 us
command evaluate-commands took 119643 us
command format took 119685 us

Git integration
This one is kind of random, but something I use frequently. :git apply --reverse<ret> is waay slower than the Vim equivalent (which is ~instant) I get through the gitsigns.nvim plugin.


I could go on, but you get the idea :slightly_smiling_face:. Not trying to nitpicking as I genuinely really like Kakoune. I’m mostly trying to understand if this is just me or an inherent limitation of Kakoune’s reliance on shell scripting. Would love to hear if other converts have run into this and how you have worked around if it it’s something you cared about.

On an up-to-date Linux system running on decade-old hardware, with my usual Kakoune configuration and after some warmup, time kak -e "quit" reports:

real	0m0.148s
user	0m0.094s
 sys	0m0.023s

That includes all the things in the standard library, plus my own customisations on top.

If you want to figure out what’s taking time at startup, you can do something like:

kak -debug 'hooks|shell|profile|commands' \
    -E "hook -once global NormalIdle .* 'set global debug %{}'"

That turns on profiling at startup, and turns it off once Kakoune is ready to accept input, so the debug buffer doesn’t keep growing as you try to inspect it.

This was an extremely helpful suggestion. Thank you! I have tried set global debug profile in the past and found that too noisy. My main discovery is that I didn’t properly understand the cost of shell expansions.

Some interesting observations:

  • On my M1 Macbook, the stock startup time with no kakrc and only system autoload is ~150-175ms.

  • Shell expansions are the main cause of slowdowns. For example, just doing declare-option str system_yank_command "%sh{ test $(uname) = Darwin && printf pbcopy || printf wl-copy }" takes 15ms on the Mac! This initially confused me since doing time uname in a normal shell takes 2ms, but doing dash -c "uname" takes much longer (25ms) and that’s closer to what’s actually happening. I was using shell expansions in a few places to branch b/w Mac and Linux, so I suspect that pruning them will speed things up. Given the lack of native branching I may end up with a mac.kak and linux.kak that sets the appropriate options.

  • Strange, but having a .config/kak/colors directory adds 30-50ms, even if it’s empty. colors/default.kak seems to be the culprit there.

  • On my weaker Linux laptop, things are much faster. An empty config starts in 75ms and my fully loaded one in ~130ms.

With this I should be able to get the Mac loading time down below 200ms which would be fast enough. In terms of my other perf gripes I think I’m going to look into git workflows that don’t rely on the editor (I find that the unstage behavior in Kakoune isn’t as polished as I would like anyways) and making formatting async.

2 Likes

Actually the unstage behavior is waaay faster on Linux :roll_eyes:. I wouldn’t be surprised if it were the case if the Mac versions of awk, find, grep, are just much slower on Mac.

1 Like

My understanding is that whenever you launch a binary that isn’t notarized by a paid-up Apple developer, macOS sends the hash to Apple HQ to check if it’s on a list of known malware. All the basic BSD utilities that ship with macOS are properly signed, of course, but things installed from Homebrew or things you compiled yourself generally aren’t. Even though dash is much smaller and lighter than bash or zsh, adding a network round-trip to each shell startup is likely to slow things down a lot.

I have heard that the hash-checking is skipped if there’s no network connection available. Even if you can’t do real work without a network connection, it might be interesting to try the time kak -e "quit" test and see if it fares any better. If it doesn’t, I’d like to know so I can stop spreading misinformation!

2 Likes

It would have been really convenient if that’s all it took, but that didn’t improve things for me so consider it debunked :slightly_smiling_face:. Maybe it does it on the first run of the binary and then it’s cached afterwards?

However, I did find that installing the GNU versions of coreutils, sed, find, and grep has dramatically improved the startup time so that it’s on par with my Linux machine. Git operations are still slower though.

I’ve added this tip to the wiki.

2 Likes

I authored this awhile back when I was having some extreme slowness with Cygwin.

Ultimately, I switched to WSL and interest in the PR died so I closed it, but it was effective in improving startup times.

could you track down what exactly is slow?
It should be somewhere in apply_selections in rc/tools/git.kak, maybe add profiling points inside of that command.
It calls a perl script, so maybe it’s perl startup on macOS? Or git.
It should be fixable

It looks like nearly all of that time is spent in patch-range.pl. Turns out, it only takes ~150ms, but it just felt slower than that. I’m not sure if things could be meaningfully improved without implementing some sort of caching as the Vim equivalent seems to do.

A :git apply --reverse calls patch-range.pl which calls git apply --reverse.
I’d guess that either perl or git startup can be slow on macOS,
especially if the executable is unsigned.
I think 150ms to start a process is still fairly high for macOS, I’d be very surprised if that was an average. But IME there’s quite high variance…
It should be doable to isolate the slowness, for example by running <a-|>git apply --reverse after selecting a diff (:diff-select-file), which should do the same thing except for the line-wise filtering.

making formatting async

I prefer to make the formatter so slow that you don’t realize you’re waiting for it.
That would play better with BufWritePre hooks (else you might want to save again after formatting is finished).

I’m not aware of a case where formatting is noticeably slow,
if there’s a good case for that I’d be interested to know.