Olical / aniseed

Neovim configuration and plugins in Fennel (Lisp compiled to Lua)
https://discord.gg/wXAMr8F
The Unlicense
606 stars 28 forks source link

Aniseed Neovim configuration statup time #24

Closed kkharji closed 3 years ago

kkharji commented 3 years ago

Hey @olical, finally after 4 days I managed to fully migrate and port my configuration to anissed, and based on our previous implicit agreement, I've measured the startuptime between my to-fnl branch and my main branch. I tried my best to port the same code, but for the most part the logic is identical. All tests are conducted with this command repeat 20 {time nvim -c "quit"}.

Summery

Without anissed on main branch, I got the following average : # user system total
av 0.091s 0.025s 0.117

With anissed on to-fnl branch, I got the following average:

# user system total
av 0.161s 0.028s 0.192

So it turns out that with aniseed, the average startuptime is 0.16s, a 0.07s increased from my main branch. However, what really got my attention is the inconsistency with the result, the highest startuptime I got is 0.25s while the lowest is 0.12s. I believe that this gap of 0.13s and the inconsistency is the direct result of doing filesystem system checks at startup. To confirm this and to check whether fennel compiler did speed things up (not quite :D) I did another test but this time without initializing anissed (i.e. not requiring aniseed.env and instead calling lua/env.init directly), and here's what I got:

Without anissed's initialization on to-fnl branch:

# user system total
av 0.098s 0.021s 0.123

Here, without aniseed initialization function, the startuptime is identical to the main branch's startuptime (with still slight increase nonetheless), however what can be seen here is that there wasn't any inconsistency or gap between startuptime like with call aniseed.env initialization method (see details section).

Conclusion ..

.. And surly, this exclusively based on my machine, the way in which things my configuration are structured and loaded and how I did my tests.

For now, and until @olical 's bright mind :D come up with something great, I created the following in init.vim

let g:call_aniseed=0
au BufWritePost */nvim/fnl/* lua require('env.core.depm').toggle_aniseed(1)
packadd aniseed
if g:call_aniseed == 1
  lua require('aniseed.env').init({ module = 'env.init' })
  lua require('env.core.depm').toggle_aniseed(0)
else
  lua require('env.init')
endif

in core.depm.depm

(defn toggle_aniseed [val] 
  (let [cmd "'s/g:call_aniseed=.*/g:call_aniseed=%d/' %s/init.vim 2>/dev/null"
        args (string.format cmd val (vim.fn.stdpath "config"))]
    (if (not= val vim.g.call_aniseed)
      (do
        (vim.cmd (.. "silent! !sed -i " args))
        (vim.api.nvim_set_var "call_aniseed" val)))))

Looking forward for your @olical comment on this, and again, thanks so much for this amazing tool.

Details

[Asciinema](https://asciinema.org/a/xlEQqTgVYVHLqA1DvQs1QxFfb)

### Without Aniseed (main branch):

| #  | user   | system  | total |
|----|--------|---------|-------|
| 01 | 0.090s | 0.040s  | 0.132 |
| 02 | 0.090s | 0.030s  | 0.118 |
| 03 | 0.100s | 0.020s  | 0.116 |
| 04 | 0.090s | 0.020s  | 0.117 |
| 05 | 0.090s | 0.020s  | 0.119 |
| 06 | 0.100s | 0.010s  | 0.121 |
| 07 | 0.100s | 0.010s  | 0.116 |
| 08 | 0.090s | 0.020s  | 0.118 |
| 09 | 0.100s | 0.020s  | 0.116 |
| 10 | 0.090s | 0.030s  | 0.117 |
| 11 | 0.100s | 0.020s  | 0.116 |
| 12 | 0.090s | 0.020s  | 0.114 |
| 13 | 0.090s | 0.030s  | 0.117 |
| 14 | 0.090s | 0.020s  | 0.115 |
| 15 | 0.090s | 0.030s  | 0.116 |
| 16 | 0.100s | 0.020s  | 0.115 |
| 17 | 0.090s | 0.030s  | 0.117 |
| 18 | 0.090s | 0.030s  | 0.116 |
| 19 | 0.070s | 0.050s  | 0.121 |
| 20 | 0.080s | 0.040s  | 0.116 |
| =  | 0.091s | 0.025s  | 0.117 | 

### With Aniseed

| #  | user   | system  | total |
|----|--------|---------|-------|
| 01 | 0.250s | 0.030s  | 0.280 |
| 02 | 0.140s | 0.020s  | 0.165 |
| 03 | 0.220s | 0.040s  | 0.258 |
| 04 | 0.140s | 0.030s  | 0.173 |
| 05 | 0.170s | 0.040s  | 0.217 |
| 06 | 0.210s | 0.040s  | 0.246 |
| 07 | 0.220s | 0.040s  | 0.257 |
| 08 | 0.140s | 0.020s  | 0.165 |
| 09 | 0.160s | 0.040s  | 0.206 |
| 10 | 0.120s | 0.040s  | 0.165 |
| 11 | 0.140s | 0.020s  | 0.163 |
| 12 | 0.150s | 0.010s  | 0.163 |
| 13 | 0.200s | 0.010s  | 0.214 |
| 14 | 0.130s | 0.020s  | 0.158 |
| 15 | 0.140s | 0.020s  | 0.162 |
| 16 | 0.130s | 0.030s  | 0.162 |
| 17 | 0.120s | 0.030s  | 0.164 |
| 18 | 0.140s | 0.020s  | 0.157 |
| 19 | 0.150s | 0.020s  | 0.173 |
| 20 | 0.160s | 0.040s  | 0.205 |
| =  | 0.161s | 0.028s  | 0.192 |

### Without initializing aniseed

| #  | user   | system  | total |
|----|--------|---------|-------|
| 01 | 0.100s | 0.030s  | 0.137 |
| 02 | 0.110s | 0.010s  | 0.123 |
| 03 | 0.110s | 0.010s  | 0.123 |
| 04 | 0.100s | 0.020s  | 0.124 |
| 05 | 0.090s | 0.030s  | 0.123 |
| 06 | 0.090s | 0.030s  | 0.123 |
| 07 | 0.110s | 0.010s  | 0.124 |
| 08 | 0.090s | 0.030s  | 0.125 |
| 09 | 0.100s | 0.020s  | 0.122 |
| 10 | 0.110s | 0.010s  | 0.127 |
| 11 | 0.100s | 0.020s  | 0.123 |
| 12 | 0.090s | 0.030s  | 0.122 |
| 13 | 0.100s | 0.020s  | 0.121 |
| 14 | 0.100s | 0.010s  | 0.123 |
| 15 | 0.090s | 0.030s  | 0.123 |
| 16 | 0.100s | 0.020s  | 0.131 |
| 17 | 0.100s | 0.020s  | 0.119 |
| 18 | 0.090s | 0.030s  | 0.119 |
| 19 | 0.090s | 0.020s  | 0.120 |
| 20 | 0.090s | 0.020s  | 0.121 |
| =  | 0.098s | 0.021s  | 0.123 |

Olical commented 3 years ago

Ooo really interesting findings! Thank you so much for doing this, I'm sure hardware makes a difference too, I run it locally on an m2 SSD which is pretty darn fast, so I should consider slower volumes too.

Fennel compiles to Lua 1:1 essentially, which is why I chose it. Really you can micro optimise like you would plain Lua but with a Lisp and macros on top. So the performance should be identical. I'm planning on adding a "production" mode to the module macros in Aniseed that spit out much smaller code. The reasoning is kinda hard to explain but there will be less Lua code on disk which is always a plus.

Thank you so much for such a thorough investigation! It's a huge help, I won't address it right away because I'm frying some other fish in Conjure land at the moment but it'll be next up. I'll try to come up with something that ticks all the boxes somehow, I just wanted to acknowledge your work first so you don't think I'm ignoring this at all.

Thank you again! I can't wait to see what improvements can come out of this :thinking: :smile:

kkharji commented 3 years ago

Hey @Olical I'm glad that you found this to be useful. Yes, storage type, cpu and gpu makes huge difference, but the first one is something we need to have out of the equation.

I'm planning on adding a "production" mode to the module macros in Aniseed that spit out much smaller code. The reasoning is kinda hard to explain but there will be less Lua code on disk which is always a plus.

Oh that is interesting, looking forward for that.

I won't address it right away because I'm frying some other fish in Conjure land at the moment but it'll be next up.

Yah take your time :P, the current hack I have works really well for my case, but nonetheless still a temporary one.

Olical commented 3 years ago

gave up on optimised macros, does make lua files a bit smaller but maybe not faster and is RISK for bugs

So I gave up on writing optimised macros since it won't make much of a difference at all and actually introduces a HUGE risk of bugs. I don't want to have to maintain two almost identical but not quite sets of macros :grimacing:

Instead I've added a way to disable automatic compilation on every load of Neovim.

lua require('aniseed.env').init({compile = false})

You can then override that by setting an environment variable.

ANISEED_ENV_COMPILE=anythingyouwant nvim

So if you were doing development in your config you can set that env var, or even set it every time you pull your dotfiles etc.

This is maybe not the final or best UX but it's an experiment for now to see what you think and see how it performs for you. I can't see much of a difference because I have a LOT of plugins so Aniseed's load time is tiny in comparison to everything else.

I wouldn't use this feature since it's just not noticeable for me at all. I don't intend for this to be the default option or behavior, it's more there for tinkerers who really care about this performance or can see a big difference on their system.

What do you think?

Olical commented 3 years ago

This is on develop, by the way.

kkharji commented 3 years ago

gave up on optimised macros, does make lua files a bit smaller but maybe not faster and is RISK for bugs

Yah me too, I tried porting my core stuff to it and it made matter worst in term of startup.

lua require('aniseed.env').init({compile = false}) Damn, It made matter worst, I did the following

if g:call_aniseed == 1
  lua require('aniseed.env').init({ module = 'start', compile = true })
  lua require('lib.wrappers').toggle_aniseed(0)
else 
  lua require('aniseed.env').init({ module = 'start', compile = false })
end

I think it might be just a personal issue and this hack i have above is what I think is the most efferent way.

It may have something todo with (compile.add-path (.. config-dir "/?.fnl")) and appending inside the condition will fix this, or (os.getenv "ANISEED_ENV_COMPILE") is causing it or perhaps

(module aniseed.compile
  {require {a aniseed.core
            fs aniseed.fs
            nvim aniseed.nvim
            fennel aniseed.fennel}})

Instead of being inside the condition it self.

In term of ux, I strongly suggest taking advantage of plugin/aniseed.vim .

set lispwords+=module
let g:call_aniseed = 0 " <--------------- changed by toggle_anissed, but is annoying due to git.
if g:call_aniseed == 1
  lua require('aniseed.env').init({ module = or vim.g.aniseed_path "env.init" })
  lua require('aniseed.env').toggle_aniseed(0) " <----------------------------------------- new function
else
  lua require(or vim.g.aniseed_path "env.init")
endif

This way the user can do :

let g:aniseed_path = "start"
packadd aniseed
Olical commented 3 years ago

Sorry about the slow loop on this, I'm spinning many plates :sweat_smile:

Damn, It made matter worst,

That's just... weird. All I've added is an if statement that gates less code, maybe the runs are just very inconsistent?

Here's some benchmarking I've done using the Aniseed time macro.

(def- config-dir (time (nvim.fn.stdpath :config)))
(time (compile.add-path (.. config-dir "/?.fnl")))

; Elapsed time: 0.01029 msecs
; Elapsed time: 0.001223 msecs

(defn init [opts]
  ;; TODO Document if it works well.
  (time
    (when (or (a.get opts :compile true)
              (os.getenv "ANISEED_ENV_COMPILE"))
      (compile.glob
        "**/*.fnl"
        (.. config-dir (a.get opts :input "/fnl"))
        (.. config-dir (a.get opts :output "/lua")))))
  (time
    (require (a.get opts :module :init))))

; Elapsed time: 0.471346 msecs                                                                                                                                                                                                              
; Elapsed time: 19.184982 msecs

So as far as I can tell, all of my time is spent loading the compiled Lua in my config, I'd imagine yours is similar too.

Saying that it also looks like requiring all the modules in the definition is also taking ~20ms.

I've refactored it a little more to make it do even less work if it can, that's about as minimal as it'll get I'm afraid! Let me know what you think!

I'll give some vim script variable flags some thought too.

Olical commented 3 years ago

I think it avoids loading the Fennel compiler now if you skip compilation which may help you out a fair bit.

Olical commented 3 years ago

I've released these changes onto master even if they're not the final step.

kkharji commented 3 years ago

I think it looks better now, I'll do some testing later. Thanks