18

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?

2
  • 6
    Try :set lazyredraw (:h lazyredraw)
    – VanLaser
    Commented Aug 13, 2015 at 1:10
  • 1
    Indeed lazyredraw is probably the best solution. You could also provide your macro and a sample file so we can see if it can be optimized.
    – statox
    Commented Aug 13, 2015 at 18:42

2 Answers 2

18

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 = &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 &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
4
  • 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
    Commented Jul 22, 2021 at 16:46
  • @D.BenKnoble Thanks. Updated the function.
    – Simba
    Commented Jul 22, 2021 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. Commented Jul 22, 2021 at 16:58
  • @ChristianBrabandt Thank you for mentioning noautocmd and eventignore. Never heard about them.
    – Simba
    Commented Jul 23, 2021 at 1:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.