carlosdc
carlosdc

Reputation: 12132

Vim macro very slow

I'm saving a very simple vim macro to a key: qa$pjq and then executing it 40000 times with 40000@a. This is very slow. I must be doing something wrong, because while it does work it takes something like 60-90 seconds. Is this as fast as vim goes? Are there some settings that can accelerate this? Is there a mixture of plugins that make macro execution slow?

I'm using a Mac and using MacVim. It is a plain text file, it really doesn't get any simpler than this.

Upvotes: 17

Views: 5308

Answers (5)

Simba
Simba

Reputation: 27568

Executing the macro 40,000 times with 40000@a is extremely slow, taking around 60-90 seconds. I must be doing something wrong.

None of the other answers addressed the primary issue. The slow performance of macro execution is mainly influenced by system clipboard access and event hooks registered by plugins, rather than by screen redrawing.

Using filetype syntax and lazyredraw only has a very minimal impact. The real solution lies in identifying and disabling the plugins that hinder your editing speed in insert mode.

Explanation

A macro simply replays actions stored in a register over the selected region. The environment in which the macro runs is the same as the environment used to record it.

This means that plugins listening to Vim events continue to operate while the macro is replayed. The relevant hooks and events affecting performance during this process include:

  • CursorMoved(I)
  • CursorHold(I)
  • InsertCharPre
  • InsertEnter
  • InsertLeave(Pre)
  • ...

Additional actions occur when moving the cursor or editing text.

Solution

  1. Avoid accessing the system clipboard when recording the macro.
  2. Suppress autocmd (event/hooks) before recording the macro, and re-enable them afterward. The following function, triggered by <F3>m, toggles this "macro boost" using the Vim setting eventignore:
function! <SID>ToggleMacro(...)
  if get(g:, '_macro', {}) ==# {}
    let g:_macro = {'state': 1}
    let g:_macro.eventignore = &eventignore
    set eventignore=all
  else
    let g:_macro.state = 0
    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>)

Note: Using :noautocmd when replaying the macro is insufficient. Since autocmds are not suppressed during macro recording, the environments for recording and replaying differ. This can lead to unexpected behavior. For example, the jiangmiao/auto-pairs plugin assists with inserting paired quotes, but it won't function correctly when replaying a macro with :noautocmd.

" q is register for macro
" noa is short for noautocmd
:noa '<,'>norm! @q

References

  • :h autocmd
  • :h noautocmd
  • :h eventignore

Special thanks to Christian Brabandt for mentioning :noautocmd and eventignore.

Upvotes: 3

Wumbo
Wumbo

Reputation: 93

I have had some problems with VIM macros being very slow even when operating on a small number of lines (although the changes are complex). se synmaxcol=1 and se ft=txt and se foldmethod=manual can help a lot. VIM does not seem smart enough to wait until after the macro is done to update syntax highlighting and folding and other such changes. It's a failing of VIM to be honest.

Upvotes: 1

Hans Dampf
Hans Dampf

Reputation: 366

I would recommend to start a blank vim with 'vim -u NONE '. Often plugins are slowing down things. You will see that it works much faster this way. Then you need to find out what plugin causes this slowdown. See also How to see which plugins are making Vim slow?

Upvotes: 10

Herbert Sitz
Herbert Sitz

Reputation: 22226

It doesn't take long to paste 40,000 lines, but if you've got the screen constantly updating as you often do in a macro that slows things down.

First, a question about what your macro is supposed to be doing. If it's simply pasting contents of default register, then proper macro definition is just qapjq. There is no need to position the cursor in a specific spot on previous line. [EDIT: sorry, I was assuming you were doing linewise paste, positioning is needed if you're pasting character-wise register at end of line. . .. ]

Second, you can speed up your current macro by setting lazyredraw (:set lazyredraw), so screen doesn't update as it goes. Probably won't speed things up a whole lot, keyboard macros like this aren't like direct processing of the buffer.

Third, here's another method that should take about a second: .,+40000g/.*/normal! $p

Upvotes: 28

Don Reba
Don Reba

Reputation: 14031

This is normal. As Herbert Sitz wrote, updating the screen is slow.

You might want to just run a substitution command: :.,+40000s/.*/&<c-r>".

  • .,+40000 is a range including the current line and the 40000 following
  • .* in the pattern matches a whole line
  • & in the string is the line that was matched
  • <c-r>" pastes the contents of the unnamed register

Upvotes: 9

Related Questions