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 Answers
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.
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. Orset 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
- ...
- Related hooks
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 todelimitMate
. - To use
:noautocmd
on selection. The range should be put beforenorm
::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
-
I don’t think any of those
execute
s are necessary. You’re just running commands. They can be entered as if they were regular commands (e.g.,silent! CocDisable
orImSelectDisable
)– D. Ben Knoble ♦Commented Jul 22, 2021 at 16:46 -
-
2you 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
andeventignore
. Never heard about them.– SimbaCommented Jul 23, 2021 at 1:20
:set lazyredraw
(:h lazyredraw
)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.