17

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.

carlosdc
  • 12,022
  • 4
  • 45
  • 62

5 Answers5

28

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

Herbert Sitz
  • 21,858
  • 9
  • 50
  • 54
  • He does not go into insert mode, just stores the macro in register `a`. Good call on screen updates, though. – Don Reba Dec 05 '11 at 04:20
  • @Don: Whoops, thanks. Then my point was just that there's no need for the $ to go to end of line. Which was probably mistaken if he's doing a character-wise paste to end of lines. . . – Herbert Sitz Dec 05 '11 at 04:39
  • Wow; this is a life saver for long macros running over large files! The time difference is an order of magnitude faster. – bigtunacan Jan 08 '14 at 19:29
10

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?

Community
  • 1
  • 1
Hans Dampf
  • 356
  • 2
  • 3
  • lightning fast! – Yann VR Oct 10 '17 at 09:21
  • Vim is an absolute weapon. Thought it was being a little sluggish today running a complex macro over a multi GB text file (taking several minutes), but no, just plugins. `vim -u NONE` before running again and it was done in a second. – jones Mar 29 '23 at 18:59
9

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
Don Reba
  • 13,814
  • 3
  • 48
  • 61
3

Reason

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.

None of the other answers targeted the major cause. 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 Christian Brabandt for mentioning :noautocmd, eventignore
Simba
  • 23,537
  • 7
  • 64
  • 76
1

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.

Wumbo
  • 83
  • 10