16

I recorded a simple macro and replayed it on following lines, but it seems replay speed is very slow (handle a very few lines per second). Is this expected and is there a way to improve such speed?

Thomson
  • 852
  • 8
  • 17

2 Answers2

17

Setting the lazyredraw with :set lazyredraw will greatly improve macro execution speed by not redrawing the screen while a macro is executing or a command is run other than those you type, see :help 'lazyredraw' for more information.

Another way to reduce make sure you don't have any time wasting autocmds or mappings. If your macros are still slow after running, check your vimrc file for slow or unnecessary mappings and remove unneeded plugins. In my case I had an autocmd that would run and external program to turn off CapsLock when exiting insert mode, this was convenient and normally unnoticeable but it slowed down insert macros a lot.

If your macro enters insert mode, where you have a lot of mappings, it may help to have a key to set paste mode and disable insert mappings temporarily, see :help 'paste' and :help 'pastetoggle'. Keep in mind this could make insertations more difficult.

Finally, keep the macro as simple as possible by minimizing movement, mode switching, etc.

Good Luck.

3

Reason

Slow macro running is mainly affected by plugins, pasting, not by screen redrawing.

filetype syntax and lazyredraw only have a very, very, very minimal impact. The correct solution should be finding out and disabling those plugins which slows down your editing in insert mode.

Macro is just replaying actions stored in the register onto the selected region. The environment of running the macro is the same as the env you record the macro. Plugin may introduce additional cost during editing. Commonly it's not a problem. But multiplying the cost with 40,000 amplifies the impact dramatically.

Here's the impact factor on macro running I got:

system clipboard >> plugins >> filetype, syntax

Solution

System Clipboard

Avoid accessing system clipboard in the macro.

Accessing external system clipboard +, * introduces additional cost, when compared with accessing internal registers. It may even freeze the macro replaying forever (run macro on 6000 lines in my test).

imap and event

Disable plugins which affects the editing speed

  • Those do insert mapping, e.g. imap <CR> <Tab> ...
    • jiangmiao/auto-pairs
    • Raimondi/delimitMate
    • ...
  • :noautocmd :norm @q to disable events temporarily during macro running. Or set eventignore=all before macro running, and set it back after.
  • ~~Those register hooks related with editing~~ (Seems not needed)
    • Related hooks
      • CursorMoved(I), CursorHold(I)
      • InsertCharPre, InsertEnter, InsertLeave(Pre)
    • neoclide/coc.nvim
    • brglng/vim-im-select
    • chrisbra/Colorizer
    • ...

Note:

  • Not all plugins registering functions on above hooks make a big differentce on macro running speed. Some of them only have a trivial, negligible impact on the editing speed. You have to find them by replaying macro with and without the plugin and comparing the time cost.
  • Insert mode mapping from auto-pairs couldn't be disabled completely, which is one of reasons I switched to delimitMate.
  • To use :noautocmd on selection. The range should be put before norm: :noa '<,'>norm! @q

Here's a toggle function I made to disable above plugins before running macro.

Use <F3>m to disables plugins before recording a macro. After running the macro, <F3>m again to reset the states of these plugins. (I group all my toggles under <F3>, m stands for macro. You can adjust the mapping for your need.)

function! <SID>ToggleMacro(...)
  " Optimize macro running by disable some plugins.
  if get(g:, '_macro', {}) ==# {}
    let g:_macro = {'state': 1}
let g:_macro.auto_pairs = get(b:, 'autopairs_enabled')
if g:_macro.auto_pairs
  let b:autopairs_enabled = 0
endif

let g:_macro.delimit_mate = get(b:, 'delimitMate_enabled')
if g:_macro.delimit_mate
  DelimitMateOff
endif

let g:_macro.eventignore = &amp;eventignore
set eventignore=all

else let g:_macro.state = 0

let b:autopairs_enabled = g:_macro.auto_pairs

if g:_macro.delimit_mate
  DelimitMateOn
endif

let &amp;eventignore = g:_macro.eventignore

endif

if g:_macro.state echo 'Macro boost: On' else echo 'Macro boost: Off' unlet g:_macro endif endfunction

nnoremap <F3>m :call <SID>ToggleMacro()<CR> command! -nargs=? ToggleMacro call <SID>ToggleMacro(<f-args>)

Ref

  • :h autocmd, :h noautocmd, :h eventignore
  • Thank @ChristianBrabandt for mentioning :noautocmd, eventignore
Simba
  • 181
  • 1
  • 5
  • I don’t think any of those executes are necessary. You’re just running commands. They can be entered as if they were regular commands (e.g., silent! CocDisable or ImSelectDisable) – D. Ben Knoble Jul 22 '21 at 16:46
  • @D.BenKnoble Thanks. Updated the function. – Simba Jul 22 '21 at 16:50
  • 2
    you should be able to use :noa! :norm! @a to execute the macro without running autocommands (or alternatively, use :set eventignore=all before running the macro. – Christian Brabandt Jul 22 '21 at 16:58
  • @ChristianBrabandt Thank you for mentioning noautocmd and eventignore. Never heard about them. – Simba Jul 23 '21 at 01:20